当前位置: 首页> 最新文章列表> 为什么 socket_accept() 一直阻塞?怎么用 socket_select() 来避免阻塞并解决这个问题?

为什么 socket_accept() 一直阻塞?怎么用 socket_select() 来避免阻塞并解决这个问题?

M66 2025-06-12

在使用 PHP 进行 Socket 编程时,很多开发者初次接触 socket_accept() 时都会遇到一个问题:。这在某些需要高并发或者多个连接同时处理的场景中会成为瓶颈。

那么,为什么会出现这种情况?我们又该如何使用 socket_select() 来避免阻塞并优雅地解决这个问题呢?

一、为什么 socket_accept() 会阻塞?

socket_accept() 是用来接受一个来自监听套接字的连接请求的函数。其原型如下:

resource socket_accept(resource $socket);

这个函数的本质是:当没有客户端连接时,它会阻塞程序执行,直到有连接到来。这是典型的阻塞式 I/O 行为。

这种行为对于单线程服务器来说,是不可取的。如果服务器因为等待一个连接而阻塞,其他任务将完全无法执行。

二、如何使用 socket_select() 来避免阻塞?

为了解决这个问题,PHP 提供了 socket_select() 函数,可以在多个套接字上进行“监听”,判断哪些套接字已经准备好进行读/写操作,从而避免阻塞等待。

socket_select() 的基本用法

int socket_select(
    array &$read,
    array &$write,
    array &$except,
    int $tv_sec,
    int $tv_usec = 0
);

你只需要将要监听的 socket 放入 $read 数组,然后调用 socket_select()。当该 socket 可读时(即有客户端连接请求时),socket_select() 就会返回,这样你再调用 socket_accept() 就不会阻塞了。

示例代码

下面是一个使用 socket_select() 的服务器端完整示例:

<?php
set_time_limit(0);

// 创建 socket
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '0.0.0.0', 8080);
socket_listen($server);

// 设置非阻塞模式(可选,但推荐)
socket_set_nonblock($server);

$clients = [];

while (true) {
    $read_sockets = [$server] + $clients;
    $write = $except = [];

    // 监听所有 sockets,超时为 1 秒
    $changed = socket_select($read_sockets, $write, $except, 1);

    if ($changed === false) {
        echo "socket_select() 发生错误\n";
        break;
    }

    // 有新的连接请求
    if (in_array($server, $read_sockets)) {
        $client = socket_accept($server);
        if ($client !== false) {
            socket_getpeername($client, $ip, $port);
            echo "新连接来自 {$ip}:{$port}\n";
            $clients[] = $client;
        }
        // 从 read 列表中移除监听 socket
        $key = array_search($server, $read_sockets);
        unset($read_sockets[$key]);
    }

    // 处理已有连接的数据
    foreach ($read_sockets as $sock) {
        $data = @socket_read($sock, 1024, PHP_NORMAL_READ);
        if ($data === false || trim($data) === '') {
            // 客户端断开连接
            $key = array_search($sock, $clients);
            unset($clients[$key]);
            socket_close($sock);
            continue;
        }

        $data = trim($data);
        echo "接收到数据:{$data}\n";

        $response = "你发送了:{$data}\n";
        socket_write($sock, $response);
    }
}
socket_close($server);

三、总结

  • socket_accept() 是阻塞的,适合简单的、同步的单连接场景;

  • 要构建一个支持并发的服务器,应当使用 socket_select() 来监听 socket 的状态;

  • socket_select() 可以让你在多个 socket 上“等待”事件,并避免因为一个阻塞操作导致整个程序挂起;

  • 建议配合 socket_set_nonblock() 使用,提高灵活性。

通过以上方式,你可以实现一个响应迅速、可同时处理多个客户端连接的 PHP Socket 服务器,为更复杂的网络编程打下基础。

在实际开发中,域名或者地址替换为自己的服务地址,例如: