PHP原生socket不支持WebSocket协议解析与心跳保活,必须使用Ratchet、Swoole等成熟框架;Ratchet需手动注入ping/pong逻辑,Swoole需显式配置onPing回调并合理设置heartbeat_idle_time与heartbeat_check_interval参数。

WebSocket 是 PHP 实时聊天的合理选择,但直接用原生 socket_create 写长连接服务极易出错——它不处理 WebSocket 握手、帧解析、掩码校验,更不支持心跳保活。真要自己写,必须用成熟扩展或框架。
为什么别手撸 WebSocket 服务器
PHP 原生 socket 不是 WebSocket server,只是 TCP server。你看到的所谓“socket + while 循环”示例,实际只完成了最基础的 TCP 数据转发,客户端发过来的是 WebSocket 协议帧(带 opcode、mask、length 等),直接 socket_read 拿到的是二进制乱码,socket_write 回去也不会被浏览器识别为合法消息。
常见错误现象包括:
- 浏览器报错
Invalid frame header或Connection closed before receiving a handshake response - 消息发出去后客户端收不到,或收到一堆乱码
- 连接空闲 30 秒后自动断开,且无重连提示
Ratchet 的 WsServer 怎么配心跳
Ratchet 是目前最稳定的 PHP WebSocket 库,但它默认不内置心跳机制,需手动注入 ping/pong 处理逻辑。
关键点:
立即学习“PHP免费学习笔记(深入)”;
-
onOpen中给每个连接设置定时器,比如每 25 秒发一次ping -
onMessage必须能识别并响应ping帧(opcode = 9),否则浏览器会因超时断连 -
onClose和onError要清理定时器,避免内存泄漏 - 不能依赖
set_time_limit(0),PHP CLI 进程需常驻,用nohup php server.php &启动
示例片段(非完整):
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
// 启动心跳:每 25 秒 ping 一次
$conn->pingTimer = \React\EventLoop\Factory::get()->addPeriodicTimer(25, function () use ($conn) {
if ($conn->isConnected()) {
$conn->send(json_encode(['type' => 'ping', 'ts' => time()]));
}
});
}
Swoole 的 heartbeat_idle_time 参数陷阱
用 Swoole 写 WebSocket 服务最省事,但它的心跳检测是「被动」的:只检查客户端是否发过 ping,不主动发 ping,也不自动回复 pong —— 这和浏览器行为不兼容。
典型问题:
- 设了
heartbeat_idle_time = 60,但客户端(Chrome)仍会在 30 秒左右断连 - 因为 Chrome 要求服务端必须在收到
ping后 30 秒内回pong,否则视为失效连接 - Swoole 默认不处理
ping帧,需显式监听onPing并调用$server->push($fd, '')回 pong -
heartbeat_check_interval必须 ≤heartbeat_idle_time / 2,否则检测滞后
配置建议:
$server = new SwooleWebSocketServer("0.0.0.0", 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
$server->set([
'heartbeat_idle_time' => 45,
'heartbeat_check_interval' => 15,
]);
前端如何配合做有效心跳
光靠服务端不够。浏览器 WebSocket 对象不会自动发 ping,必须手动实现:
- 连接建立后立即启动
setInterval,每 20–25 秒发一次ws.send(JSON.stringify({type:'ping'})) - 监听
onmessage,收到{type:'pong'}就刷新本地 lastPong 时间戳 - 如果超过 40 秒没收到 pong,主动
ws.close()并触发重连逻辑 - 不要用
ws.ping()—— 浏览器 WebSocket API 根本没有这个方法
容易忽略的一点:onclose 事件里不能直接重连,要加 1–3 秒退避,否则网络抖动时会雪崩式重连。



















