当前位置: 首页> 最新文章列表> socket_accept() 配合 socket_set_timeout 实现连接超时控制

socket_accept() 配合 socket_set_timeout 实现连接超时控制

M66 2025-05-28

在 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())来实现真正的连接等待超时。

二、实现思路

  1. 设置监听 socket 为非阻塞模式
    这样调用 socket_accept() 不会阻塞,如果没有连接请求,会立即返回 false

  2. 使用循环+延时配合超时判断
    在循环里反复调用 socket_accept(),如果返回 false,判断是否超时,超时则退出等待。

  3. 一旦接收到连接,使用 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 服务端程序。