workerman中udp与tcp的根本区别在于协议语义:udp无连接、不维护连接状态、需显式传地址发包且无重传,tcp面向连接、自动管理生命周期、支持直接send但需处理粘包。

Workerman 中 UDP 和 TCP 的根本区别不在框架层,而在协议语义和底层 socket 行为——你用的是 udp:// 还是 tcp://,决定了整个通信模型。
UDP 服务无法维持长连接状态
UDP 是无连接的,Worker 启动后监听一个端口,但不会为每个客户端维护连接对象。收到数据时,onMessage 回调里的 $connection 实际是一个轻量级的 UdpConnection 实例,它只保存了客户端的 ip 和 port,不带生命周期管理(没有 onConnect/onClose)。这意味着:
- 你不能在
onMessage外部缓存$connection并期望它长期有效 - 主动发包必须显式传入目标地址:
$connection->send($data, $client_ip, $client_port) - 若客户端位于 NAT 后,5 分钟无交互,Linux 内核可能丢弃该五元组会话,导致后续发包失败(使用
stream_socket时更明显)
TCP 服务天然支持连接生命周期管理
TCP 是面向连接的,Workerman 会为每个新连接创建一个持久的 TcpConnection 对象,并触发 onConnect;断开时触发 onClose。这个连接对象可被安全地存入全局数组或 Channel,用于后续广播或定向推送:
-
$connection->send()不需要额外参数,直接复用已建立的 socket 句柄 - 连接对象自带心跳、超时、缓冲区控制等能力(如
$connection->close()、$connection->isClosed()) - 粘包问题必须处理:TCP 是字节流,
onMessage收到的数据可能是半包、整包或粘连的多包,需靠协议解析(如自定义decode)切分
UDP 发送必须“记住”客户端地址,TCP 不用
这是最常踩的坑:UDP 服务端首次收到客户端数据后,必须立刻把 $connection->getRemoteIp() 和 $connection->getRemotePort() 记下来,否则下次想回包就找不到目标。而 TCP 的 $connection 本身已绑定远端地址,随时可发。
- UDP 示例片段:
$worker->onMessage = function ($connection, $data) { $client_ip = $connection->getRemoteIp(); $client_port = $connection->getRemotePort(); // 必须显式传入地址才能发回去 $connection->send("ack", $client_ip, $client_port); }; - TCP 示例片段:
$worker->onMessage = function ($connection, $data) { // 直接 send,无需地址参数 $connection->send("ack"); }; - UDP 若忘记存地址,又没做心跳保活,60 秒后发包大概率静默失败(无错误提示,包直接被内核丢弃)
UDP 没有重传/拥塞控制,TCP 有但需注意阻塞风险
UDP 的“快”是双刃剑:它不保证送达,也不排队、不重试、不控速。适合音视频、SIP、GB28181 这类容忍丢包但忌延迟的场景。TCP 虽可靠,但在 Workerman 中要注意:
- 单个
TcpConnection的send()是异步写入缓冲区,但如果对方接收慢或网络卡顿,缓冲区满后会阻塞整个事件循环(尤其在未启用buffer_size限流时) - UDP 的
send()几乎总是立即返回(除非系统发送缓冲区爆满),但不等于对方收到了 - UDP 无法感知丢包,调试时得靠抓包(
tcpdump -i any udp port 5060)或应用层 ACK 机制
真正容易被忽略的是:UDP 的“无状态”不是便利,而是责任转移——你得自己实现地址记忆、心跳保活、应用层确认、乱序容忍。TCP 把这些扛在协议栈里,但换来了粘包、缓冲区管理和连接可靠性开销。选哪个,取决于你的业务能不能接受“发了但不知道到了没”,以及愿不愿意为“到了但可能乱序/重复”写解析逻辑。











