PHP不原生支持WebSocket客户端长连接,需用Workerman等框架构建常驻进程实现握手、帧解析、心跳与重连;不可用cURL或Guzzle模拟,否则会因协议不支持或缺少事件循环而失败。

PHP 本身不原生支持 WebSocket 客户端长连接(fsockopen 或 stream_socket_client 只能做一次握手+单次通信),所谓“PHP 连 WebSocket 并保持长连”,实际是指用 PHP 启动一个常驻进程,自行处理 WebSocket 握手、帧解析、心跳、重连——不是靠 curl 或普通 HTTP 请求能搞定的。
WebSocket 客户端在 PHP 中必须自己实现帧收发
PHP 没有像 Node.js 的 ws 或 Python 的 websockets 那样开箱即用的异步 WebSocket 客户端库。主流方案是基于 ReactPHP 或 Workerman 构建事件循环,手动完成:
- 发送 RFC6455 格式的握手请求(含
Sec-WebSocket-Key和 base64 签名) - 解析服务端返回的 101 响应并确认升级成功
- 按 WebSocket 帧格式(MASK + PAYLOAD LEN + DATA)接收/解包消息
- 定时发送 ping 帧(opcode
0x9),并响应 pong(0xA)
直接用 file_get_contents 或 cURL 访问 ws:// 地址会报错:Unsupported protocol: ws —— 因为它们根本不支持 WebSocket 协议栈。
推荐用 Workerman 实现稳定长连(非阻塞 + 心跳保活)
Workerman 是国内最成熟的 PHP 长连接解决方案,它内置 WebSocket 客户端组件,自动处理帧、ping/pong、重连逻辑。关键点:
立即学习“PHP免费学习笔记(深入)”;
- 必须用
Worker::runAll()启动常驻进程,不能跑在 FPM/CLI 一次性脚本里 - 连接失败时,
$client->onClose触发后需主动调用$this->reconnect(),不能依赖超时自动重试 - 心跳间隔建议设为 25–30 秒(避开多数代理/防火墙 30s 空闲断连阈值)
- 不要在
onMessage中执行耗时操作(如数据库查询),否则阻塞整个事件循环
示例片段(简化):
$client = new \Workerman\Connection\AsyncTcpConnection('ws://echo.websocket.org');
$client->onConnect = function($connection) {
echo "Connected\n";
};
$client->onMessage = function($connection, $data) {
echo "Received: $data\n";
};
$client->onClose = function($connection) {
echo "Disconnected, reconnecting...\n";
// 手动重连逻辑需在此处触发
};
$client->connect();
别用 Guzzle 或 curl 尝试“模拟” WebSocket 长连
常见误区:用 GuzzleHttp\Client 发起 GET 请求,加 Upgrade: websocket 头。这只会得到一个 400 或 501 错误,因为:
- Guzzle 底层仍是 HTTP/1.1 请求-响应模型,无法维持底层 TCP 连接并持续读取二进制帧
- 即使握手成功(极少数服务允许纯 HTTP 升级),后续也无法解析 mask、fin、opcode 等字段
- 没有事件循环,无法监听 socket 是否可读/可写,必然卡死或报
Operation now in progress
如果你只是想“从 PHP 后端向 WebSocket 服务发一条指令”,正确做法是:让前端 JS 直连 WS,PHP 通过 REST API 通知前端,再由前端发 WS 消息;或者用 Redis Pub/Sub 做桥接,避免 PHP 直连 WS。
真正可靠的 PHP WebSocket 客户端长连,本质是写一个守护进程,而非常规 Web 请求。漏掉事件循环、帧解析或心跳机制中的任意一环,连接大概率在 30–60 秒内被中间设备静默断开,且无错误提示。











