不能用同一个worker监听多个协议,因为worker构造时绑定的协议地址(如http://、websocket://、tcp://)决定了底层协议类和回调参数类型,混用会导致握手失败、乱码或连接闪断;正确做法是每个协议起独立worker实例,端口隔离、各司其职,并通过redis或gatewayworker注册中心协同。

Workerman 做多协议网关,不是靠一个 Worker 实例“同时处理多种协议”,而是靠多个独立 Worker 实例监听不同端口、各司其职,再通过共享状态(如 Redis)或统一注册中心(如 GatewayWorker)协同工作。强行让一个连接处理 HTTP + WS + TCP 混合流量,会直接破坏协议解析逻辑,导致握手失败、乱码、连接闪断。
为什么不能用同一个 Worker 监听多个协议?
Worker 构造时传入的协议地址(如 '@#@#@#@#@#@#@#@#@#@0'、'websocket://0.0.0.0:8080'、'tcp://0.0.0.0:9501')决定了底层使用的协议类和事件回调行为:
-
http://触发onMessage回调,参数是$request对象,自动解析 header/body -
websocket://也走onMessage,但参数是解帧后的文本/二进制数据,且必须先完成 WebSocket 握手 -
tcp://的onMessage收到的是原始字节流,无协议解析,需自行处理粘包/分包 -
udp://没有连接概念,只有onMessage(收包)和sendTo(发包),不支持onConnect/onClose
若你写成:
$worker = new Worker('tcp://0.0.0.0:8080');
却期望它能正确响应 HTTP GET 请求 —— 它只会把 GET / HTTP/1.1\r\n... 当作裸字节收进来,不会返回 HTTP/1.1 200 OK,浏览器打不开。
正确做法:端口隔离 + 协议专精
每个协议起一个独立 Worker,绑定专属端口,互不干扰:
-
HTTP网关:监听@#@#@#@#@#@#@#@#@#@1,做反向代理或 API 路由 -
WebSocket网关:监听websocket://0.0.0.0:8081,处理长连接推送 -
TCP网关:监听tcp://0.0.0.0:8082,对接私有设备协议(如自定义心跳+数据帧) -
UDP网关:监听udp://0.0.0.0:8083,接收传感器上报(无连接、低延迟场景)
关键点:
- 所有 Worker 共享同一份启动逻辑(
Worker::runAll()),但彼此进程/连接完全隔离 - 不要在
onMessage里做阻塞操作(如file_get_contents、sleep、未加async的 Redis 同步调用),否则卡死整个进程 - UDP Worker 无法用
$connection->close()(没连接),发送必须用$worker->sendTo($ip, $port, $data) - TCP Worker 若需处理变长包,必须实现自定义
protocol类,重写input()判断包边界,否则必然粘包
如何让不同协议的客户端“看到同一个服务”?
用户不需要感知后端协议差异,靠两层解耦:
前端统一域名 + Nginx 分流(最轻量)
gateway.example.com由 Nginx 根据 path 或 upgrade header 分流:location /api { proxy_pass @#@#@#@#@#@#@#@#@#@2; }location /ws { proxy_set_header Upgrade $http_upgrade; proxy_pass @#@#@#@#@#@#@#@#@#@3; }location /tcp-tunnel { return 405; } # TCP 不走 HTTP,需另配端口直连后端统一注册中心(适合 GatewayWorker 架构)
如果用GatewayWorker,HTTP/WS/TCP 可都作为“Gateway”接入,共用同一个registerAddress(如text://127.0.0.1:1236),由BusinessWorker统一处理业务逻辑,此时协议差异被收敛在 Gateway 层,BusinessWorker只管数据内容,不管来源。
注意:GatewayWorker 默认不支持 HTTP 协议,需手动扩展或改用纯 Worker + Redis 状态同步替代。
UDP 和 TCP 共存时最容易忽略的坑
- UDP 没有连接状态,
onConnect/onClose根本不会触发,别在这两个回调里写日志或清理资源 - TCP 的
$connection->getRemoteIp()在 NAT 后可能不准,UDP 的sendTo必须显式传$ip和$port,不能依赖连接上下文 - 同一机器上 UDP 和 TCP 监听相同端口(如都用
:8080)会冲突,操作系统不允许 - UDP 包大小受 MTU 限制(通常 ≤1472 字节),超长包会被静默丢弃,应用层要切片或换 TCP
多协议网关真正的复杂点不在启动几个 Worker,而在于——协议语义如何对齐:HTTP 的 request-id、WS 的 connection-id、TCP 的 socket-id、UDP 的 ip:port 四元组,怎么在业务层映射成统一的 session_id?这必须靠外部存储(如 Redis hash)来桥接,不能依赖内存变量或 PHP 进程内全局状态。











