udp onmessage 的 $connection 是 udpconnection 实例,仅封装当前数据包来源,不支持长连接;必须用 $connection->send("msg", $connection->getremoteaddress()) 显式回包,且无 onconnect/onclose 事件,状态需自行基于 ip+端口维护。

UDP onMessage 的 $connection 参数不是 TCP/WebSocket 那种连接对象
Workerman 的 UDP onMessage 回调里,$connection 实际是 Worker\Connection\UdpConnection 实例,它不表示“长连接”,而只是当前数据包的来源封装。你不能对它调用 $connection->send() 后还指望对方能持续收消息——UDP 本身无连接,每次 send() 都必须显式指定目标地址。
常见错误是直接照搬 TCP 写法:$connection->send("ack"),结果对方根本收不到。因为没传地址,底层会丢弃。
-
$connection->getRemoteAddress()返回数组['ip' => '192.168.1.100', 'port' => 54321],必须用这个地址回包 - 正确回发方式是:
$connection->send("ack", $connection->getRemoteAddress()) - 如果只做单向接收(比如日志收集),甚至可以完全不调用
send()
收到的数据是原始二进制,不是自动解码的字符串
UDP 不带协议头,Workerman 不会帮你解析或转码。无论网关发的是 JSON 字符串、十六进制指令还是 Protobuf 序列化数据,$data 都是原样 string(PHP 中即二进制字节流)。
容易踩的坑:直接 json_decode($data) 失败,但打印 bin2hex($data) 发现开头是 efbbbf(BOM),或者末尾多了不可见控制字符(比如 \x00 或换行符)。
- 先检查长度:
strlen($data)是否符合预期协议定义的包长 - 去除首尾空白:
trim($data, "\x00 \t\n\r\0"),尤其注意 C 风格字符串结尾的\x00 - 如果是 UTF-8 JSON,用
json_decode($data, true, 512, JSON_INVALID_UTF8_IGNORE)容错解析
UDP onMessage 没有“连接建立”和“断开”事件
UDP 是无状态协议,所以 onConnect 和 onClose 在 UDP Worker 中**完全不会触发**。别在代码里写这两个回调并期待它们执行——它们会被 Workerman 忽略,也不会报错,只是静默失效。
这意味着你无法靠“连接生命周期”来管理客户端上下文(比如 session、心跳计时器)。所有状态必须基于 IP+端口元组自行维护,且要处理地址复用(NAT 后多个设备可能共用同一出口 IP)。
- 用
$connection->getRemoteAddress()作为键,存到全局数组或 Redis 中(注意并发安全) - 定期清理超时地址,例如用
Worker\Lib\Timer::add()扫描过期条目 - 不要依赖
$connection->id做唯一标识——UDP 连接对象每次收包都新建,id无意义
高并发下 UDP 包丢失或乱序,应用层得自己兜底
Workerman 的 UDP 性能很高,但无法改变 UDP 协议本质:不保证送达、不保序、不重传。如果你的业务要求“每条指令必须到达”,就不能只靠 onMessage 被调用就认为成功。
典型场景如设备上报心跳、传感器采样值,服务端需配合 ACK 机制或滑动窗口。单纯记录日志或监控指标类场景可接受丢失。
- 收到关键指令后,应立即异步发回 ACK 包(用
$connection->send(..., $addr)) - 避免在
onMessage里做耗时操作(如 MySQL 查询),否则阻塞事件循环,导致后续 UDP 包被内核缓冲区丢弃 - Linux 下可通过
net.core.rmem_max调大 UDP 接收缓冲区,缓解突发丢包











