workerman udp 不支持广播,因udp无连接故$worker->connections为空;需用原生socket设so_broadcast向子网广播地址发包,并采用三次广播+指数退避及时间戳校验提升设备发现可靠性。

Workerman 本身不支持 UDP 广播,它只支持单点响应式 UDP 通信;想实现局域网设备自动发现,必须自己构造广播包并手动发送,且不能依赖 $worker->connections 遍历——因为 UDP 没有连接概念,$worker->connections 在 UDP Worker 中始终为空。
为什么 $worker->connections 对 UDP 无效
UDP 是无连接协议,Workerman 的 Worker('udp://...') 实例在收到数据时,会临时创建一个 UdpConnection 对象传入 onMessage 回调,但不会将其加入 $worker->connections 数组。这个数组只用于 TCP/WebSocket 这类有状态连接的生命周期管理。
常见错误现象:foreach ($worker->connections as $conn) 循环根本不会执行,或返回空数组,导致“广播”逻辑静默失效。
- UDP 场景下,所有客户端地址信息只存在于
$connection->getRemoteAddress()中,且仅在onMessage触发时有效 - 若想“向所有人广播”,必须自己维护一份已知设备地址列表(如 IP+端口),或直接向子网定向广播地址发包
- 不能复用 TCP 那套“遍历 connections”的惯性思维,否则调试半天也看不到包发出
如何从 Workerman UDP Server 主动发广播包
Workerman 的 UDP Worker 默认只能响应式回包(即 $connection->send() 只能发回给刚发来数据的那个源地址)。要主动广播,得绕过 UdpConnection,直接用 PHP 原生 socket 发送。
实操建议:
- 在
onMessage外部(如 Worker 启动后)用socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)创建独立 socket,并立即设置SO_BROADCAST选项:socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, 1) - 广播目标地址必须是子网定向广播地址(如
192.168.1.255),不是255.255.255.255—— 后者在多数 Linux 发行版和 Windows Hyper-V 环境下会被内核丢弃 - 发送时用
socket_sendto($sock, $data, strlen($data), 0, $broadcast_ip, $port),注意端口需与监听端口一致,否则接收方可能忽略 - 避免在
onMessage里频繁创建/销毁 socket,应复用已创建的 socket 实例
设备发现协议必须处理的三个现实问题
纯靠一次 UDP 广播做设备发现,在真实局域网中失败率极高。Wi-Fi 干扰、交换机 IGMP snooping、Windows 防火墙默认拦截、Docker 虚拟网卡路由异常都会导致包不见踪影。
可靠做法是组合设计:
- 广播请求包里带时间戳(如
microtime(true))和随机 nonce,防止旧包重放;响应包必须原样 echo 这两个字段 - 采用“三次广播 + 指数退避”:第一次发完等 200ms,无响应则 400ms 后再发,第三次间隔 800ms;总耗时控制在 1.5 秒内,不影响用户体验
- 接收端必须绑定
0.0.0.0(即IPAddress.Any在 C# 中的等价写法),且显式启用SO_BROADCAST;若本机也要收自己发的广播,还需设SO_LOOPBACK(PHP 中为IP_MULTICAST_LOOP,但对广播无效,实际需靠系统级 loopback 设置)
真正难的不是发出去,而是让不同品牌网卡、虚拟化环境、防火墙策略下的设备都能稳定收得到。广播地址算错一位、socket 选项漏设一个、时间戳没校验,都可能导致某批设备永远“看不见”。











