Workerman的UDP和TCP服务必须分属不同Worker实例且不可混用协议,否则触发Address already in use错误或UDP收不到数据;多进程UDP需用stream_socket模式,客户端地址需手动缓存用于主动回包,跨协议通信须依赖Redis等外部存储。

Workerman 的 UDP 和 TCP 服务可以同时运行,但必须分属不同 Worker 实例,且不能共用同一个端口或协议栈监听方式。 否则会触发 Address already in use 错误,或者 UDP 收不到数据(尤其在多进程下)。
UDP 和 TCP 必须用独立的 Worker 实例
Workerman 不允许单个 Worker 实例混用协议。TCP 监听用 "tcp://0.0.0.0:8080",UDP 就得另起一个 Worker,比如 "udp://0.0.0.0:8081"。
- 错误写法:
$worker = new Worker("tcp://0.0.0.0:8080"); $worker->transport = 'udp';—— 无效,transport属性只对 GatewayWorker 生效,原生 Worker 不识别 - 正确写法:两个变量、两个
new Worker(),各自绑定协议和端口 - 注意:TCP 和 UDP 即使端口号相同(如都用
8080),Linux 也视为不同套接字,但不建议这么做——容易引发运维混淆和防火墙策略冲突
UDP 多进程下必须用 stream_socket 模式
Workerman 在 UDP 场景中,count > 1 时默认使用 stream_socket_server 创建 socket;而 count === 1 时可能回退到 socket_create。前者支持多进程复用同一端口(通过内核 SO_REUSEPORT),后者不支持,会导致子进程启动失败或仅第一个进程收包。
- 显式启用多进程 UDP:设置
$udp_worker->count = 4;,无需额外操作,Workerman 自动选stream_socket - 若发现只有 master 进程收到 UDP 包,检查是否误设
count = 1或手动调用了底层socket_*函数干扰了初始化 - UDP 没有连接状态,
$connection对象的$connection->remoteAddress和$connection->remotePort是唯一标识客户端的依据,务必在onMessage中读取并缓存(如需后续主动回包)
共享数据需绕过进程隔离
TCP 和 UDP 的 Worker 是完全独立的进程组,彼此内存不共享。如果想让 TCP 服务触发 UDP 广播,或把 UDP 收到的设备心跳同步给 TCP 在线列表,不能直接传变量。
- 推荐用
Workerman\Lib\Timer+ 全局静态数组仅适用于单进程调试,上线必崩 - 生产环境必须走外部媒介:Redis(用
phpredis扩展)、SQLite(轻量级 IPC)、或Workerman\Channel(需额外启动 Channel Server) - 避免用文件轮询或 MySQL 表锁做协调——UDP 高频场景下 I/O 成为瓶颈,且事务延迟不可控
- 特别注意:UDP 客户端地址是“无状态”的,
sendto()必须每次指定目标地址,不能像 TCP 那样靠$connection->send()简单调用
端口占用和防火墙要分开配
系统层面,TCP 和 UDP 端口是分开管理的。开了 firewall-cmd --add-port=8080/tcp,不代表 8080/udp 也通。
- 检查是否真在监听:
ss -tuln | grep ':8080'—— 你会看到两行,一行tcp,一行udp - Docker 用户注意:
-p 8080:8080默认只映射 TCP,UDP 需显式写-p 8080:8080/udp - 云服务器安全组(如阿里云、腾讯云)必须分别放行 TCP 和 UDP 对应端口,缺一不可
最容易被忽略的是:UDP 没有三次握手,也没有连接保活机制,客户端发一个包后若长时间无交互,中间 NAT 设备可能直接丢弃映射表项。所以哪怕只是做设备注册,也得在 UDP 服务里加心跳逻辑——不是可选项,是上线前提。

















