CodeIgniter控制器能实现长轮询因其基于HTTP请求-响应模型,PHP进程阻塞等待后退出,符合PHP-FPM生命周期;而WebSocket需常驻连接、脱离HTTP协议栈,故不适用。

CodeIgniter 本身不提供长轮询(Long Polling)的内置支持,但可以安全、可控地在控制器中实现——关键在于避免超时、正确管理响应流、不依赖会话自动续期。
为什么 CodeIgniter 控制器能跑长轮询,而 WebSocket 不行
长轮询本质仍是 HTTP 请求:客户端发一个 GET /api/long-poll,PHP 进程保持阻塞直到有数据或超时;请求结束后进程退出,完全符合 PHP-FPM 生命周期。这和 WebSocket 的常驻连接、脱离 HTTP 协议栈有根本区别——所以它能在 CI 控制器里跑,且无需额外服务进程。
常见错误是直接套用普通 AJAX 路由逻辑,没处理好连接保持和响应头,导致浏览器提前断连或服务端强制 close。
- 必须显式禁用输出缓冲:
ob_end_clean()+ignore_user_abort(true) - 必须设置合适的超时时间(如 30 秒),不能依赖
max_execution_time默认值 - 不能在长轮询逻辑中调用
$this->session->set_userdata()等写会话操作,否则可能触发 session 文件锁阻塞
CI 3.x 中实现长轮询的最小可行控制器
以下是一个可直接放入 application/controllers/Api.php 的示例,监听 Redis 中的频道变化(也可换成数据库轮询):
class Api extends CI_Controller
{
public function long_poll()
{
// 关键:关闭所有输出缓冲,防止提前 flush
@ob_end_clean();
ignore_user_abort(true);
set_time_limit(30);
<pre class="brush:php;toolbar:false;"> // 设置响应头,声明为事件流(兼容性更好)
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 模拟等待新消息(实际应监听 Redis 或查询 DB)
$start = time();
$message = null;
while ($message === null && (time() - $start) redis->get('notification:' . $this->input->get('user_id'));
}
if ($message) {
echo "data: " . json_encode(['msg' => $message]) . "\n\n";
ob_flush();
flush();
} else {
echo "data: " . json_encode(['status' => 'timeout']) . "\n\n";
ob_flush();
flush();
}
}}
注意:$this->redis 需提前在构造函数中初始化(不能靠 autoload 加载),因为长轮询期间 CI 的自动加载机制可能不可靠;Redis 实例必须是持久连接(new Redis() 后调用 connect(),而非 pconnect(),后者在 CLI 模式下行为不稳定)。
前端发起长轮询并自动重连的 JS 写法
浏览器原生 EventSource 对长轮询支持有限(它只认 SSE 格式,且自动重连策略不可控),更稳妥的是用 fetch + 递归调用:
function startLongPoll(userId) {
fetch(`/api/long_poll?user_id=${userId}`)
.then(res => res.json())
.then(data => {
console.log('received:', data);
// 处理消息后立即发起下一轮
startLongPoll(userId);
})
.catch(err => {
console.warn('poll failed, retry in 1s:', err);
setTimeout(() => startLongPoll(userId), 1000);
});
}
不要用 XMLHttpRequest 手动管理 readyState——现代浏览器对长时间 open 状态的 xhr 支持不一致,容易被代理或 CDN 中断;fetch 在多数环境更稳定,且配合 AbortController 可主动取消。
长轮询在 CI 项目中的真实瓶颈点
真正卡住的不是代码逻辑,而是并发连接数和资源占用:
- Apache + mod_php 下,每个长轮询请求独占一个 worker 进程,200 个并发就吃光默认配置
- Nginx + PHP-FPM 必须调大
pm.max_children和pm.max_requests,否则频繁重启导致连接中断 - 数据库轮询方式(比如
SELECT ... WHERE created_at > ?)在高并发下易产生间隙锁争用,建议改用 Redis 的BRPOP或PUB/SUB作为消息中转
如果你发现长轮询在本地 OK、上生产就大量超时,先查 PHP-FPM 的 slowlog 和 access.log 中 504 状态码,而不是急着改控制器代码——90% 的问题出在服务器层连接池配置上。











