在使用 PHP 构建基于 socket 的服务端或客户端程序时,长连接(persistent connection)是一种非常常见的需求。尤其在进行即时通讯、在线游戏服务器或数据推送等场景下,保持 socket 长时间连接不断线对性能和用户体验都有显著影响。
在这种场景中,合理使用 socket_clear_error() 是保持连接稳定的一个关键手段。本文将深入探讨其使用方式,并结合一些实际例子说明如何避免由于错误未清除而导致的连接中断。
socket_clear_error() 是 PHP 提供的一个 socket 操作函数,用于清除与指定 socket 关联的错误信息。其函数签名如下:
void socket_clear_error ([ resource $socket ] )
它可以作用于一个具体的 socket,也可以全局使用。当一个 socket 操作出错时,错误信息会被存储,如果不及时清除这些错误,在下一次调用 socket 函数时可能会影响行为,甚至导致连接断开。
在使用长连接时,导致断线的原因有很多,例如:
网络抖动;
客户端关闭连接;
服务端内部处理错误未捕获;
错误堆积未清除。
其中最后一种是我们本文要解决的重点。即使是一个轻微的非致命错误,如果没有通过 socket_clear_error() 清除,下一次执行如 socket_write()、socket_read() 时就可能因为检测到“错误状态”而异常返回或触发中断。
下面是一个典型的 PHP Socket 服务端代码片段,用于处理客户端请求,并保持长连接:
$host = '0.0.0.0';
$port = 9000;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, $host, $port);
socket_listen($socket);
$clients = [];
while (true) {
$read = $clients;
$read[] = $socket;
$write = $except = null;
socket_select($read, $write, $except, 0, 200000); // 非阻塞
if (in_array($socket, $read)) {
$client = socket_accept($socket);
$clients[] = $client;
socket_clear_error($client); // 清除新连接的错误状态
}
foreach ($clients as $key => $client) {
$data = @socket_read($client, 1024, PHP_NORMAL_READ);
if ($data === false) {
$error = socket_last_error($client);
if ($error !== 0) {
echo "Socket error: " . socket_strerror($error) . "\n";
socket_clear_error($client);
}
continue;
}
$data = trim($data);
if ($data == 'quit') {
socket_close($client);
unset($clients[$key]);
continue;
}
$response = "Server received: $data\n";
@socket_write($client, $response, strlen($response));
socket_clear_error($client); // 每次通信后清除错误
}
}
这段代码说明了几点关键策略:
每次接收到客户端连接后立刻使用 socket_clear_error() 清除状态;
每次读取或写入操作后都清除可能的错误;
对于非致命错误(比如临时网络中断)不立刻断开连接,而是尝试清除后继续处理。
除了清除错误外,合理设计“心跳包”也是保持连接不断的关键。心跳机制可以让客户端每隔固定时间向服务端发送一个轻量的数据包,如果超过一定时间没有接收到心跳,就可以主动关闭连接。
// 简化版心跳响应示例
if ($data == 'ping') {
@socket_write($client, "pong\n");
socket_clear_error($client);
}
这样配合 socket_clear_error() 可以更好地检测出“真实断线”与“误判断线”的区别。
长时间不清除错误还会造成所谓“僵尸连接”,即 socket 对象虽然仍然存在,但已经处于无效状态。以下是一个定期检查所有客户端状态的办法: