PHP本身不支持原生WebSocket服务端,必须依赖Swoole、Workerman或Ratchet等常驻进程异步框架;因PHP-FPM为短生命周期设计,无法处理Upgrade头完成握手,也不能维持长连接,故在Apache/Nginx+PHP-FPM下运行server.php必然失败。

PHP本身不支持原生WebSocket服务端,必须依赖常驻进程的异步框架或库;直接在Apache/Nginx + PHP-FPM下运行server.php会失败,这是绝大多数新手踩的第一个坑。
为什么不能在PHP-FPM里跑WebSocket服务器
PHP-FPM是为短生命周期HTTP请求设计的:每个请求独占一个进程,响应完立即释放资源。而WebSocket需要维持数小时甚至数天的长连接,持续监听、收发帧、管理状态——这在FPM模型里会导致进程卡死、内存暴涨、连接数迅速耗尽。
- 现象:
php server.php在终端能跑,但用curl或浏览器访问ws://地址时握手失败,返回HTTP 400 Bad Request或直接断连 - 根本原因:FPM不处理Upgrade头,无法完成WebSocket握手(RFC-6455要求的
Connection: Upgrade+Upgrade: websocket流程) - 正确做法:必须用CLI模式启动独立进程,且该进程要基于事件循环(如ReactPHP、Swoole、Workerman)
Ratchet启动脚本里IoServer::factory()的三层包装含义
IoServer::factory(new HttpServer(new WsServer(new Chat())), 8080)不是套娃,每一层都有不可替代的职责:
-
Chat:你的业务逻辑类,实现MessageComponentInterface,只管“收到消息怎么处理”“谁连进来了” -
WsServer:协议层,负责解析/打包WebSocket帧、校验掩码、处理ping/pong、区分文本/二进制数据类型 -
HttpServer:握手层,拦截初始HTTP请求,检查Sec-WebSocket-Key,计算并返回Sec-WebSocket-Accept,再把连接移交WsServer
漏掉任意一层,连接就建立不起来。比如只写new WsServer(new Chat()),服务会直接报错Call to undefined method WsServer::listen(),因为WsServer本身不绑定端口。
立即学习“PHP免费学习笔记(深入)”;
客户端连不上ws://localhost:8080的常见排查点
即使服务端代码无误,客户端仍可能连不上,问题往往出在环境或配置上:
- 防火墙/安全组:确认本地
8080端口未被系统防火墙(如ufw)、云厂商安全组或公司网络策略拦截 - HTTPS页面限制:若前端页面是
https://,浏览器禁止其连接ws://(非加密WebSocket),必须改用wss://并配置TLS证书,或临时用http://页面测试 - 路径匹配:Ratchet默认不处理URL路径,
ws://localhost:8080/chat和ws://localhost:8080/对它没区别;但如果你用了Nginx反向代理,location /chat必须透传Upgrade头,否则握手失败 - 错误日志位置:Ratchet默认不输出错误到文件,加
error_log('xxx', 3, '/tmp/ratchet.log');到onError方法里才能看到真实异常
生产环境必须加的心跳与连接清理机制
WebSocket连接不像HTTP有超时自动断开,客户端后台挂起、网络中断、杀进程后,服务端$clients里的ConnectionInterface对象不会自动销毁,导致内存泄漏和僵尸连接累积。
- 必须实现心跳:在
onOpen里用$conn->send('ping')触发,并在onMessage中识别'pong'响应,超时未回复则$conn->close() - 不能只靠
onClose:该回调只在客户端主动关闭或网络正常断开时触发;断网重连、休眠唤醒等场景下,onClose根本不会执行 - 推荐方案:用
React\EventLoop\Timer\IntervalTimer每30秒遍历$clients,调用$conn->checkConnection()(Ratchet内置方法)检测底层socket是否存活
这个检查逻辑很容易被忽略,上线一周后连接数缓慢爬升、内存占用翻倍,问题根源往往就在这里。



















