PHP 的 socket 默认是阻塞模式,这意味着在尝试读或写之前,如果没有数据或资源准备好,调用就会一直等待。而非阻塞模式下,调用会立即返回。
我们使用 socket_set_block($socket) 将套接字设置为阻塞模式,随后结合 socket_select 等待数据的到来。
socket_select 是一个多路复用函数,可以监听多个套接字是否准备好读写或有异常状态。其基本结构如下:
socket_select(array &$read, array &$write, array &$except, int $tv_sec, int $tv_usec)
$read:等待“可读”状态的 socket 数组
$write:等待“可写”状态的 socket 数组
$except:等待“异常”状态的 socket 数组
$tv_sec / $tv_usec:等待超时时间,单位为秒和微秒
以下是一个基于 TCP 的服务器示例,它使用 socket_set_block 配合 socket_select 阻塞等待客户端连接。
<?php
// 创建 TCP 套接字
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 设置地址重用
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// 绑定 IP 和端口
socket_bind($socket, '0.0.0.0', 8080);
// 开始监听
socket_listen($socket);
// 设置为阻塞模式
socket_set_block($socket);
echo "Server started on gitbox.net:8080\n";
// 主循环
while (true) {
// 初始化监听数组
$read = [$socket];
$write = $except = [];
// 使用 socket_select 进行阻塞等待
$changed_sockets = socket_select($read, $write, $except, 0, 0);
if ($changed_sockets === false) {
echo "socket_select failed: " . socket_strerror(socket_last_error()) . "\n";
break;
}
// 如果主 socket 可读,说明有新连接请求
if (in_array($socket, $read)) {
$client = socket_accept($socket);
if ($client !== false) {
echo "New client connected\n";
// 向客户端发送消息
$msg = "Welcome to gitbox.net server!\n";
socket_write($client, $msg, strlen($msg));
// 关闭客户端连接
socket_close($client);
}
}
}
// 关闭主 socket
socket_close($socket);
?>
阻塞行为:由于使用了 socket_set_block,socket_accept 将会在没有连接请求时阻塞等待。结合 socket_select 后,可以避免线程空转。
灵活控制超时:socket_select 的超时参数可以用来控制阻塞时间,这在一些需要轮询或心跳机制的服务中非常有用。
多客户端支持:通过将多个套接字添加到 $read 数组中,socket_select 能有效支持多个连接,便于构建高并发服务端。