UdpConnection 不能用作客户端,因其仅为框架内部入站连接句柄且构造函数私有;需用 PHP 原生 socket 或 stream_socket_client 实现 UDP 客户端通信,并注意超时设置与非阻塞处理以避免阻塞 Workerman 进程。

UdpConnection 本身不提供「客户端连接」语义,Workerman 的 UDP Worker 默认是服务端角色 —— 它监听端口、接收任意来源的 UDP 数据包,并通过 $connection->send() 回复。真要模拟“客户端行为”,得绕开 Worker 的连接管理,直接用 PHP 原生 socket。
为什么不能直接 new UdpConnection 当客户端
UdpConnection 是 Workerman 内部封装的「入站连接句柄」,只在 onMessage 回调中由框架自动创建并传入。它没有构造函数对外暴露,也不支持主动绑定远端地址或发起 sendto。试图手动实例化会报 Fatal error: Uncaught Error: Call to private UdpConnection::__construct()。
用 PHP 原生 socket 发起 UDP 请求
这是最轻量、最可控的方式,适合发心跳、查状态、对接第三方 SIP/GB28181 设备等场景:
- 用
socket_create(AF_INET, SOCK_DGRAM, SOL_UDP)创建 socket - 用
socket_sendto()直接向目标 IP:Port 发包,无需 connect - 用
socket_recvfrom()接收响应(注意设超时,避免阻塞) - 记得
socket_close(),尤其在常驻进程里反复调用时防 fd 泄漏
示例片段:
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 1, 'usec' => 0]); socket_sendto($sock, $data, strlen($data), 0, '192.168.1.100', 5060); $len = socket_recvfrom($sock, $buf, 2048, 0, $ip, $port); socket_close($sock);
在 Workerman 进程里安全调用原生 socket
Workerman 的 event loop(ReactPHP 或 libevent)和原生 blocking socket 冲突。直接在 onMessage 里用 socket_recvfrom 会卡住整个进程。必须满足:
- 只用于「发包 + 等少量响应」,且严格控制超时(如上面的
SO_RCVTIMEO) - 避免在高并发 onMessage 中频繁创建/销毁 socket,可考虑复用 socket 资源池
- 若需异步等待响应,应改用
Worker::$globalEvent->add()注册可读事件,配合非阻塞 socket - 不要在定时器(
Timer::add)里裸写 blocking recv,容易堆积
替代方案:用 stream_socket_client 模拟 UDP 客户端
PHP 的 stream_socket_client('udp://host:port', ...) 可以返回一个 stream resource,配合 stream_socket_sendto() 和 stream_select() 实现非阻塞收发。但要注意:
- 它底层仍调用系统 socket,行为与原生 socket 一致
-
stream_socket_client的 UDP 模式不建立连接,只是方便封装 - 无法像 TCP 那样用
fwrite/fread,必须用stream_socket_sendto显式指定地址 - 错误处理更松散,比如目标不可达时可能静默失败,需检查返回值
实际用起来比原生 socket 少几行代码,但可控性略低。对简单探测类请求够用。
UDP 客户端通信在 Workerman 里本质是「借壳执行」,框架不托管生命周期。最容易踩的坑是忘记设 recv 超时,或者在 event loop 里混用 blocking IO —— 这会导致整个 Worker 进程假死。真正需要高频双向交互的场景(比如 GB28181 心跳保活),建议把客户端逻辑抽成独立子进程,或改用 Swoole 的 Co\Socket。

















