Docker环境PHP运行UDP服务socket_sendto报Unknown host错误
问题:请告知调用
socket_sendto函数时为何会触发Unknown host错误
运行环境
centos 7.9 php-fpm 7.4 AND php-fpm 8.1 部署于docker容器内
/etc/hosts配置
<docker net ip> <docker container id> 127.0.0.1 nd.domain.com
启动方式
php -q SERVER.php
服务源码
#!/usr/local/bin/php -q <?php // error_reporting(E_ALL); // set_time_limit(0); // ob_implicit_flush(); $address = '0.0.0.0'; $port = 80; echo "server now start\r\n"; $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP) or dir("create failed this socket"); if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) { echo 'Unable to set option on socket: '. socket_strerror(socket_last_error()) . PHP_EOL; } socket_bind($socket, $address, $port) or dir("Failed to bind to (".$address.":".$port.")"); echo "server binded\r\n"; $messageBuffer = ""; $clients = []; while (true){ if (@socket_recvfrom($socket, $messageBuffer, 2048, MSG_WAITALL, $ip, $port)) { $address = "$ip:$port"; echo "Receive data : " . $messageBuffer . " FROM " . $address. "\r\n"; if (!isset($clients[$address])) $clients[$address] = array(); $data = "hello-world"; $client_ip = gethostbyname($address); echo "callback - send to " . $client_ip . "\r\n"; if (@socket_sendto($socket, $data, strlen($data), 0, $client_ip, $port)) { echo "sendto ok"; } else { echo socket_strerror(socket_last_error($socket))."\r\n"; }; } else { echo socket_strerror(socket_last_error($socket))."\r\n"; }; } socket_close($socket); ?>
服务运行日志
php-fpm 8.1(开启错误显示)环境日志
server now start server binded Receive data : hello world FROM 123.123.123.12:12345 callback - send to 123.123.123.12:12345 Warning: socket_sendto(): Host lookup failed [-10000]: Unknown error -10000 in [err-path] on line 47 Unknown error -10000
php-fpm 7.4(关闭错误显示)环境日志
Receive data : 0101010101! FROM 123.123.123.12:12345 callback - send to 123.123.123.12:12345 Unknown host
错误原因
触发错误的核心是代码逻辑有两处明显问题:
socket_recvfrom接收到数据时,会把客户端的IP和端口分别写入$ip、$port两个变量,这里的$port和开头定义监听端口的$port是全局同名变量,直接覆盖了原来的监听端口值,属于变量污染。- 你把客户端IP和端口拼接成了
IP:端口格式的字符串(比如日志里的123.123.123.12:12345),再把这个拼接后的字符串传入gethostbyname()做域名解析。gethostbyname()只接受纯域名、纯IP格式的入参,传入带端口的拼接字符串会直接解析失败,返回原字符串。 - 最后你把解析失败、带端口的字符串作为目标IP传入
socket_sendto(),函数无法识别这种非法格式的目标地址,就会抛出Unknown host错误,PHP8.1环境下还会同步抛出主机查找失败的-10000警告。
修复方案
- 把
socket_recvfrom写入的客户端IP、端口变量改名,和全局监听端口变量区分开,避免变量覆盖。 - 去掉多余的
gethostbyname调用:socket_recvfrom返回的$ip本身就是可直接使用的纯客户端IP,不需要再做解析,更不能把带端口的拼接字符串传入解析函数。
修复后的核心代码段参考:
$listen_address = '0.0.0.0'; $listen_port = 80; // ... 省略socket创建、绑定逻辑,绑定使用$listen_address、$listen_port while (true){ // 客户端地址变量单独命名,避免和监听配置冲突 if (@socket_recvfrom($socket, $messageBuffer, 2048, MSG_WAITALL, $client_ip, $client_port)) { $client_addr = "$client_ip:$client_port"; echo "Receive data : " . $messageBuffer . " FROM " . $client_addr. "\r\n"; if (!isset($clients[$client_addr])) $clients[$client_addr] = array(); $data = "hello-world"; echo "callback - send to " . $client_ip . ":" . $client_port . "\r\n"; // 直接用拿到的纯客户端IP、端口发数据,不需要额外解析 if (@socket_sendto($socket, $data, strlen($data), 0, $client_ip, $client_port)) { echo "sendto ok\r\n"; } else { echo socket_strerror(socket_last_error($socket))."\r\n"; }; } else { echo socket_strerror(socket_last_error($socket))."\r\n"; }; }
内容的提问来源于stack exchange,提问作者lIIlIllIIlllIIlI
相关产品推荐
相关产品推荐

