在使用 PHP 开发基于 socket 的网络应用时,开发者常常会接触到 socket_accept() 和 socket_set_block() 这两个函数。虽然这两个函数都属于 PHP 的 socket 扩展,但它们在使用上有着密切的关系,理解它们的协同工作机制,有助于我们更精准地控制 socket 的行为,提升网络通信的可靠性和效率。
在探讨这两个函数的协作前,我们先来了解它们的基本作用:
socket_accept(resource $socket): resource|false
此函数用于接受一个来自监听 socket 的连接请求。如果没有连接请求,它的行为取决于 socket 的阻塞模式。
socket_set_block(resource $socket): bool
此函数将一个 socket 设置为阻塞模式。在阻塞模式下,相关的 socket 操作(如 socket_accept、socket_read 等)会等待直到有数据或连接发生。
与 socket_set_block() 相对的是 socket_set_nonblock(),它会使得 socket 操作立即返回,不论是否有数据或连接到达。
默认情况下,PHP 创建的 socket 是阻塞模式的,这意味着 socket_accept() 会一直阻塞等待直到有客户端连接。也就是说,如果你运行如下代码:
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '0.0.0.0', 12345);
socket_listen($socket);
$client = socket_accept($socket);
echo "Connection accepted";
在没有客户端连接的情况下,程序会一直停在 socket_accept() 调用处,直到有连接发生。
现在我们引入 socket_set_block(),显式地设置 socket 为阻塞模式(虽然默认就是阻塞的,但显式设置可以防止代码因前面操作改变了模式而出错):
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_block($socket); // 显式设置为阻塞模式
socket_bind($socket, '0.0.0.0', 12345);
socket_listen($socket);
$client = socket_accept($socket);
echo "Connection accepted";
此时,socket_accept() 的行为没有变化:它仍然会阻塞直到有客户端连接。
但如果我们在此处使用 socket_set_nonblock():
socket_set_nonblock($socket);
那么调用 socket_accept() 时,如果没有连接请求,它会立刻返回 false,并且可以通过 socket_last_error() 检查是否是因为 EAGAIN 或 EWOULDBLOCK,从而实现非阻塞接收连接的逻辑。
socket_set_block() 和 socket_accept() 之间的协作,本质上是通过设置 socket 的工作模式,决定 socket_accept() 的阻塞行为。这个设置决定了程序的控制流程是同步的(阻塞式)还是异步的(非阻塞式)。
例如,在编写高性能服务器时,你可能会使用非阻塞 socket 并结合 socket_select() 来监听多个连接请求:
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($socket);
socket_bind($socket, '0.0.0.0', 12345);
socket_listen($socket);
$clients = [];
while (true) {
$read = [$socket];
$write = $except = null;
if (socket_select($read, $write, $except, 1) > 0) {
$client = socket_accept($socket);
if ($client !== false) {
$clients[] = $client;
echo "Client connected\n";
}
}
// 处理其他逻辑
}
这种方式允许你在一个循环中同时处理多个 socket,无需在 socket_accept() 上阻塞等待。
socket_set_block() 决定了 socket 是否阻塞工作,而 socket_accept() 是一个受此设置影响的具体操作。当 socket 是阻塞模式时,socket_accept() 会等待连接;而在非阻塞模式下,它会立即返回。两者的协同使用让我们可以根据实际需求编写同步或异步的网络服务程序,提供了更大的灵活性。
了解它们之间的关系,不仅有助于写出稳定的 socket 服务,也为构建高并发、低延迟的网络应用奠定了基础。
如需查看更多相关示例,可参考 https://gitbox.net/socket-examples。