PHP无法用普通脚本运行WebSocket服务,因其CGI/FPM模式“请求-响应-退出”生命周期极短,无法维持长连接、管理状态或处理帧;必须依赖Swoole、Workerman等常驻进程方案。

PHP 本身不支持原生 WebSocket 服务器,必须依赖扩展或第三方库启动常驻进程;直接用 fsockopen 或 stream_socket_server 手写握手和帧解析极易出错,不推荐生产环境使用。
为什么不能用普通 PHP 脚本跑 WebSocket 服务
WebSocket 是长连接协议,要求服务端持续监听、管理连接状态、处理 ping/pong、帧解包/封包。而传统 PHP CGI/FPM 模式是“请求来→执行→响应→退出”,生命周期极短,无法维持连接。
常见错误现象包括:
- 浏览器报
WebSocket connection to 'ws://...' failed: Error during WebSocket handshake - 服务端脚本执行完就退出,
netstat -an | grep :8000看不到监听端口 - 握手返回的
Sec-WebSocket-Accept值计算错误(Base64 + SHA1 拼接规则写错)
所以必须用能长期运行的方案:Swoole 扩展、Ratchet、Workerman 或 Webman。
立即学习“PHP免费学习笔记(深入)”;
Swoole 是目前最省心的 PHP WebSocket 方案
它把底层事件循环、TCP/HTTP/WebSocket 协议栈都封装好了,不用手动处理握手、心跳、分帧,性能也远超纯 PHP 实现。
实操要点:
- 确认已安装 Swoole:运行
php --ri swoole,看到support websocket => enabled才算到位 -
swoole_websocket_server必须在 CLI 模式下启动,不能通过 Apache/Nginx 直接访问 .php 文件 - 监听地址建议用
0.0.0.0(而非127.0.0.1),否则局域网其他设备连不上 - 务必设置
'heartbeat_check_interval' => 60和'heartbeat_idle_time' => 600,否则闲置连接不会自动断开
最小可运行示例(server.php):
use Swoole\WebSocket\Server;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
$server = new Server('0.0.0.0', 9501);
$server->on('open', function (Server $server, Request $request) {
echo "新连接 fd={$request->fd}\n";
});
$server->on('message', function (Server $server, Frame $frame) {
$server->push($frame->fd, '收到:' . $frame->data);
});
$server->on('close', function (Server $server, int $fd) {
echo "连接 {$fd} 关闭\n";
});
$server->start();
Ratchet 适合需要深度控制协议细节的场景
它是纯 PHP 实现,不依赖扩展,调试方便,但性能弱于 Swoole,且需手动处理 ReactPHP 事件循环、HTTP 握手包装等。
容易踩的坑:
- 必须用
react/http+react/ws包装 WebSocket 层,单独ratchet/pawl只是客户端 - 握手失败时,
onOpen不触发,但错误日志可能被 React 静默吞掉 —— 建议在onError里加var_dump($connection->getCloseReason()) - 消息体超过 125 字节会自动分帧,但 Ratchet 默认不合并,
$frame->getPayload()可能只拿到片段 - 不支持 TLS/SSL 直连,必须前置 Nginx 反向代理并配置
proxy_set_header Upgrade $http_upgrade;
关键依赖安装命令:composer require cboden/ratchet react/http react/socket
Nginx 反向代理 WebSocket 必须显式透传 Upgrade 头
哪怕你用 Swoole 或 Ratchet 启了服务,前端通过域名访问时,Nginx 默认会切断 WebSocket 升级流程。
正确配置片段(放在对应 location 块内):
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_pass http://127.0.0.1:9501;
漏掉任意一行,都会导致浏览器报 Error during WebSocket handshake: Unexpected response code: 200。
另外注意:Swoole 默认不走 HTTP 中间件,所以 proxy_pass 后面不能带路径(如 http://127.0.0.1:9501/api/ws),只能指向根路径。
真正麻烦的从来不是“怎么连上”,而是连接保活、异常重连、广播范围控制、用户身份绑定和消息可靠性 —— 这些在 Swoole 的 onClose、connections 遍历、bind 方法里要自己补全逻辑,Ratchet 则得靠外部存储(Redis)做连接映射,别指望框架自动帮你管好每个 $fd 对应谁。



















