workerman 4.0.20 通过常驻进程与事件循环实现 php 高并发:调用 worker::runall() 启动永不退出的 while(true) 循环,自动选择 epoll/kqueue/select;绑定 onconnect/onmessage 等回调处理连接与数据;定时器基于最小堆管理;onerror 专捕网络异常,与 onclose 互斥。

要让 PHP 服务像 Node.js 那样持续响应海量连接而不卡死,必须绕过传统 FPM 的阻塞式请求生命周期——Workerman 4.0.20 正是通过重构事件循环内核与回调分发链路,把 PHP 变成真正能“守着 socket 等事发生”的常驻服务。
事件循环如何启动并保持运行
Worker 实例调用 Worker::runAll() 后,Master 进程 fork 出 Worker 子进程,每个子进程立即进入独立事件循环;【该循环永不退出,除非进程被 kill 或显式调用 stop()】。
底层自动选择最优 I/O 多路复用引擎:Linux 下优先加载 ext-epoll(若未安装则降级为 select),macOS 使用 kqueue,Windows 强制走 select —— 这个决策在 Events/Select.php 初始化时完成,无需手动指定。
事件循环主体是一个 while(true) 循环,每轮执行五步:注册监听 → 等待内核通知 → 收集就绪事件 → 分发回调 → 清理定时器。其中“等待内核通知”这一步实际调用的是 stream_select() 或 epoll_wait(),毫秒级超时控制精度达 1ms。
连接事件如何绑定到回调函数
方法一:直接赋值属性
$worker->onConnect = function($connection) { echo "IP: {$connection->getRemoteIp()}"; };
方法二:使用 on() 方法注册(兼容旧版写法)
$worker->on('connect', function($connection) { ... });
方法三:继承 Worker 类重写 handleEvent()(高级定制场景)
注意:所有回调函数必须是 callable 类型,且不能包含阻塞操作(如 file_get_contents、sleep、mysqli_query 同步调用);【一旦某个 onMessage 回调里执行了 500ms 以上的同步 DB 查询,整个 Worker 进程的其他 9999 个连接都会被卡住】。
事件触发与分发的完整路径
第一步:客户端发起 TCP 握手,内核将新连接放入 accept 队列
第二步:事件循环检测到监听 socket 可读,立即调用 accept() 创建 TcpConnection 实例,并触发 onConnect
第三步:客户端发送数据,内核标记该连接 socket 可读,事件循环调用 recv() 读取原始字节流
第四步:数据进入协议层(如 WebSocketProtocol),经 input() 判断包边界后交由 decode() 解析,最终把结构化数据传入 onMessage
第五步:若回调中调用了 $connection->send(),数据先写入内存缓冲区,事件循环下一轮检测到 socket 可写时才真正调用 send() 发出
定时器事件如何嵌入事件循环
Workerman 4.0.20 将定时器完全整合进 EventLoop,不再依赖 pcntl_alarm 或信号模拟:
调用 Timer::add(1.5, function() { echo 'every 1.5s'; }); 会在内部创建一个最小堆(Min-Heap)管理所有定时任务,每次事件循环迭代前检查堆顶是否到期。
重复定时器(repeat)和延迟定时器(delay)共享同一套触发逻辑,但 repeat 会自动重入堆中;【deleteAllTimer() 会清空当前 Worker 进程内所有定时器,包括已触发未执行完的】。
定时器回调执行期间,事件循环暂停监听 I/O,确保时间精度;但回调本身仍受单线程限制,不可长时间运行。
错误事件与连接异常如何捕获
当 socket 出现 ECONNRESET、EPIPE 或 read/write 失败时,事件循环不会崩溃,而是封装为 ConnectionException 并触发 onError 回调。
$worker->onError = function($connection, $code, $msg) { error_log("Conn {$connection->id} error {$code}: {$msg}"); };
连接断开不等于 onError 被调用:正常断开走 onClose,仅网络异常或系统资源不足才走 onError;【onClose 和 onError 是互斥的,同一个连接生命周期内最多触发其中一个】。
如果未设置 onError,异常会被静默吞掉,仅记录到 STDERR —— 生产环境务必显式定义。











