必须用swoole\http\server而非websocket\server,因其底层http解析器可自动识别upgrade头并分流:非升级请求走request事件,websocket握手成功后触发open事件,实现单端口http与ws复用。

直接用 Swoole\Http\Server,不是 Swoole\WebSocket\Server —— 后者只能处理 WebSocket,不支持 HTTP 路由;前者才是正确起点,它原生支持 HTTP + WebSocket 复用。
为什么必须用 Swoole\Http\Server
Swoole\WebSocket\Server 是一个精简协议栈,只解析 WebSocket 帧,遇到普通 GET / 或 POST /api/login 请求会直接丢弃,返回空响应或 400 错误。而 Swoole\Http\Server 底层基于 HTTP 解析器,能识别 Upgrade: websocket 头并自动触发 open 事件,其余请求走 request 回调 —— 这才是复用的底层能力。
- 错误做法:
new Swoole\WebSocket\Server(...)+ 手动解析request_uri→ 无法捕获非升级类 HTTP 请求,$request对象根本不存在 - 正确路径:所有逻辑统一注册到
Swoole\Http\Server实例上,靠事件分发隔离协议 - 注意:
Swoole\Http\Server在 v4.8+ 默认启用协程,无需额外开启,但若手动Co::set()调整协程栈大小,需确保不低于 2M(WebSocket 帧解包可能占用较多内存)
on("request") 和 on("open") 怎么共存不冲突
两个事件完全正交:request 只响应非 WebSocket 升级的 HTTP 请求(如 HTML、API、健康检查),open 仅在客户端发送合法 Upgrade 请求且服务端成功响应 101 Switching Protocols 后触发。Swoole 内部已按 RFC6455 自动完成协议分流,你不需要、也不应该在 request 里手动判断 Upgrade 头再转发。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 典型误操作:在
request回调里写if ($request->header['upgrade'] === 'websocket') { ... }→ 这属于重复造轮子,且容易漏掉Sec-WebSocket-Key校验等细节,导致握手失败 - 健康检查必须走
request:比如GET /health返回200 OK,不能放在open里,因为浏览器发起 WebSocket 连接前不会先 GET 这个路径 -
open回调里的$req参数是Swoole\Http\Request类型,可读取 query、cookie、header,但不可调用$response->end()—— 此时响应已发出,只能用$server->push()发送帧
HTTPS + WSS 场景下 Nginx 反向代理的关键配置
如果前端有 Nginx,且要支持 wss://,必须透传 WebSocket 升级头,否则客户端连接会卡在 pending 状态,日志里看到 net::ERR_CONNECTION_REFUSED 或超时。
- 缺一不可的三行:
proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade" - 不要加
proxy_set_header Host $host到后端 Swoole —— 它不依赖 Host 路由,反而可能干扰$request->server['request_uri']的路径匹配 - 证书必须配在 Nginx 层,Swoole 自身不处理 TLS;若强行用
Swoole\Http\Server的ssl配置,需额外加载证书文件且无法热更新,运维成本高 - 测试是否生效:用
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://your-domain.com/ws,应返回101 Switching Protocols,而不是 200 或 404
最容易被忽略的是:Swoole 的 request 事件里不能调用阻塞 IO(如 file_get_contents、同步 MySQL 查询),否则整个进程会卡住;WebSocket 连接数越多,HTTP 响应延迟越明显。该用协程 MySQL 客户端就用,该投递 Task 就投递,别把「同时处理」误解成「混在一起乱跑」。










