在使用 PHP 进行 Socket 编程时,很多开发者初次接触 socket_accept() 时都会遇到一个问题:。这在某些需要高并发或者多个连接同时处理的场景中会成为瓶颈。
那么,为什么会出现这种情况?我们又该如何使用 socket_select() 来避免阻塞并优雅地解决这个问题呢?
socket_accept() 是用来接受一个来自监听套接字的连接请求的函数。其原型如下:
resource socket_accept(resource $socket);
这个函数的本质是:当没有客户端连接时,它会阻塞程序执行,直到有连接到来。这是典型的阻塞式 I/O 行为。
这种行为对于单线程服务器来说,是不可取的。如果服务器因为等待一个连接而阻塞,其他任务将完全无法执行。
为了解决这个问题,PHP 提供了 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 服务器,为更复杂的网络编程打下基础。
在实际开发中,域名或者地址替换为自己的服务地址,例如: