在 PHP 中,使用原生 socket 编程时,socket_accept() 函数用于接受客户端的连接请求,但默认情况下,这个函数是阻塞式的——也就是说,如果没有客户端连接,程序会一直等待下去,无法自动超时。为了避免程序因等待连接而卡死或无响应,我们可以结合 socket_set_timeout() 来实现连接超时控制,从而提升程序的健壮性。
本文将详细介绍如何利用 socket_accept() 和 socket_set_timeout() 来实现 PHP 连接超时控制的功能。
socket_accept($socket)
阻塞地等待客户端连接请求,如果有连接请求则返回新的 socket 资源,否则一直阻塞。
socket_set_timeout($socket, $seconds, $microseconds)
为指定的 socket 设置超时时间。超时后,读取/写入操作会返回超时错误。
需要注意的是,socket_set_timeout() 是针对读写操作的超时控制,并不能直接控制 socket_accept() 的阻塞等待时间。因此,我们需要配合其他方式(例如非阻塞模式或使用 socket_select())来实现真正的连接等待超时。
设置监听 socket 为非阻塞模式
这样调用 socket_accept() 不会阻塞,如果没有连接请求,会立即返回 false。
使用循环+延时配合超时判断
在循环里反复调用 socket_accept(),如果返回 false,判断是否超时,超时则退出等待。
一旦接收到连接,使用 socket_set_timeout() 设置读写超时
保证后续数据传输过程中有超时控制。
<?php
$host = "0.0.0.0";
$port = 12345;
$timeoutSeconds = 10; // 最大等待连接时间
// 创建 TCP socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
die("socket_create failed: " . socket_strerror(socket_last_error()) . "\n");
}
// 绑定端口
if (socket_bind($socket, $host, $port) === false) {
die("socket_bind failed: " . socket_strerror(socket_last_error($socket)) . "\n");
}
// 监听端口
if (socket_listen($socket, 5) === false) {
die("socket_listen failed: " . socket_strerror(socket_last_error($socket)) . "\n");
}
// 设置监听 socket 为非阻塞
socket_set_nonblock($socket);
$startTime = time();
$clientSocket = false;
echo "等待客户端连接,超时时间:{$timeoutSeconds}秒\n";
while (true) {
$clientSocket = @socket_accept($socket);
if ($clientSocket !== false) {
echo "客户端已连接!\n";
break;
}
// 判断是否超时
if ((time() - $startTime) >= $timeoutSeconds) {
echo "等待客户端连接超时。\n";
break;
}
// 休眠 100ms 避免 CPU 占用过高
usleep(100000);
}
if ($clientSocket !== false) {
// 设置客户端 socket 读写超时,例如 5 秒
socket_set_option($clientSocket, SOL_SOCKET, SO_RCVTIMEO, ["sec"=>5, "usec"=>0]);
socket_set_option($clientSocket, SOL_SOCKET, SO_SNDTIMEO, ["sec"=>5, "usec"=>0]);
// 读取客户端数据示例
$buf = '';
$bytes = socket_recv($clientSocket, $buf, 2048, 0);
if ($bytes === false) {
echo "接收数据失败或超时:" . socket_strerror(socket_last_error($clientSocket)) . "\n";
} else {
echo "收到客户端数据:" . $buf . "\n";
}
socket_close($clientSocket);
}
socket_close($socket);
?>
非阻塞监听:通过 socket_set_nonblock(),避免 socket_accept() 阻塞,允许我们自定义超时逻辑。
循环等待连接:每次调用 socket_accept(),如果无连接立即返回 false,判断是否达到超时条件,若未超时则继续等待。
连接后设置读写超时:使用 socket_set_option() 给客户端 socket 设置读写超时,防止后续读取数据时永久阻塞。
usleep() 减少 CPU 占用:避免空循环导致 CPU 占用过高。
通过将监听 socket 设置为非阻塞,并结合循环轮询判断时间,实现了对 socket_accept() 阻塞等待连接的超时控制。同时,在接收连接后利用 socket_set_option() 设置读写超时,保证数据传输过程的稳定性和安全性。
这套方案简单实用,非常适合需要手工管理 socket 连接超时的 PHP 服务端程序。