PHP-FPM/Apache下无法实现WebSocket长连接,因进程请求即销毁;应使用CLI模式或ReactPHP/Swoole等事件驱动方案,并手动处理心跳、超时及系统级资源调优。

PHP 作为客户端连接 WebSocket 时根本不是“长连接”
PHP 进程天生是无状态、请求即销毁的,fsockopen 或 stream_socket_client 建立的 WebSocket 连接,在脚本结束时自动关闭。你无法靠一个 PHP-FPM worker 持有连接数小时——这不是瓶颈,是机制限制。
常见错误现象:Connection closed before receiving a handshake response 或反复重连却收不到消息,本质是脚本执行完就断了,根本没机会持续收包。
- 别用 PHP-FPM / Apache mod_php 做 WebSocket 客户端长期监听
- 若必须用 PHP 发起连接(如一次性发指令),用 CLI 模式运行,并手动管理
stream_select轮询 - 真实场景中,应把长连接交给
ReactPHP、Amp或Swoole这类事件驱动运行时
stream_socket_client 默认阻塞 + 无心跳导致连接悄无声息断开
原生 PHP 的 socket 客户端默认阻塞,且不处理 WebSocket 协议帧(如 ping/pong)。服务端超时踢人、NAT 超时、中间代理静默丢包时,PHP 进程完全感知不到连接已失效,下次 fwrite 才报错。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 设置
stream_set_timeout($fp, 5)避免卡死 - 手动解析响应头验证 WebSocket 握手成功(检查
Sec-WebSocket-Accept) - 自己实现定时
ping帧(0x9 类型)并等待pong(0xA),否则多数生产环境撑不过 60 秒 - 不要依赖
feof()判断断连——它常返回 false 直到真正读失败
Swoole 里 Co\Http\Client 不支持 WebSocket 协议升级
很多开发者误以为 Swoole 的协程 HTTP 客户端能直连 WebSocket,但 Co\Http\Client 只做 HTTP/1.1 请求,不处理 Upgrade: websocket 和密钥协商。直接用它发 upgrade 请求只会收到 400 或空响应。
正确路径:
- 用
Swoole\Coroutine\Http\Client发起普通 HTTP 请求后,**立刻切换为原始 socket**:调用$client->getSocket()获取底层 socket,再手动发送 WebSocket 握手头 - 或改用
Swoole\Coroutine\WebSocket\Client(Swoole ≥ 4.4.0),它内置协议解析和 ping 自动回复 - 注意
WebSocket\Client的push()默认不校验帧格式,发错二进制数据会触发服务端断连
单进程并发连接数受系统 ulimit -n 和 TCP TIME_WAIT 累积影响
即使改用 Swoole 协程,当并发建连 >1000 时,可能遇到 Too many open files 或大量 TIME_WAIT 占满端口。这不是 PHP 代码问题,是操作系统级约束。
关键参数和动作:
- 调高
ulimit -n(建议 ≥ 65535)并写入/etc/security/limits.conf - 降低内核
net.ipv4.tcp_fin_timeout(从 60 改为 30)加快回收 - 启用
net.ipv4.tcp_tw_reuse = 1允许 TIME_WAIT 套接字重用于新连接(客户端场景安全) - 避免在循环里频繁
new Client+connect,复用协程客户端实例
真正的性能分水岭不在 PHP 语法或函数选型,而在是否绕开了进程模型枷锁、是否主动管理连接生命周期、以及是否让系统资源瓶颈暴露出来再针对性调优。漏掉任意一层,压测时都会在 200 QPS 左右突然跌停。










