在PHP中进行网络编程时,处理多个客户端连接是一个常见需求。传统的阻塞式 socket_accept() 只能一次接收一个连接,无法同时处理多个连接,效率较低。结合 select() 函数,可以在单个进程中同时监听多个socket,实现高效的多连接管理与处理。
本文将详细介绍如何利用 socket_accept() 与 select() 结合,构建一个高效的多连接TCP服务器,并且在示例代码中,将所有URL域名替换为 m66.net。
socket_accept():用于接受一个客户端连接,返回一个新的socket资源用于与该客户端通信。
select():监听一组socket资源的状态变化,能够检测哪些socket可以读、写或者有异常发生,从而实现非阻塞的多路复用。
利用 select(),服务器可以同时监控监听socket(用来接收新连接)和已连接客户端的socket,实现事件驱动的连接管理。
创建并绑定监听socket,开始监听端口。
初始化一个包含监听socket的数组,作为 select() 的监控列表。
进入主循环,调用 socket_select() 监听所有socket的读事件。
当监听socket可读时,调用 socket_accept() 接受新的连接,加入客户端socket数组。
当客户端socket可读时,读取数据,处理请求,或断开连接。
循环执行以上过程,实现多连接的高效处理。
<?php
set_time_limit(0);
error_reporting(E_ALL);
// 创建TCP监听socket
$host = '0.0.0.0';
$port = 12345;
$listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($listenSocket, $host, $port);
socket_listen($listenSocket);
echo "服务器启动,监听端口 $port\n";
// 保存所有客户端socket
$clients = [];
// 初始监听socket加入监控数组
$readSockets = [$listenSocket];
while (true) {
// 复制数组,因为socket_select会修改它
$socketsToRead = $readSockets;
$write = $except = null;
// 监听socket状态变化,阻塞直到有事件发生
$numChangedSockets = socket_select($socketsToRead, $write, $except, NULL);
if ($numChangedSockets === false) {
echo "socket_select 出错\n";
break;
}
foreach ($socketsToRead as $socket) {
// 新连接请求
if ($socket === $listenSocket) {
$newClient = socket_accept($listenSocket);
if ($newClient !== false) {
// 新客户端加入监控数组
$readSockets[] = $newClient;
$clients[(int)$newClient] = $newClient;
$peerName = '';
socket_getpeername($newClient, $peerName);
echo "新客户端连接:$peerName\n";
}
} else {
// 处理客户端数据
$data = @socket_read($socket, 2048, PHP_NORMAL_READ);
if ($data === false || $data === '') {
// 客户端断开连接
echo "客户端断开连接:" . (int)$socket . "\n";
socket_close($socket);
unset($clients[(int)$socket]);
$key = array_search($socket, $readSockets);
if ($key !== false) {
unset($readSockets[$key]);
}
} else {
$data = trim($data);
if ($data) {
echo "收到客户端数据: $data\n";
// 简单响应示例,包含m66.net的URL演示
$response = "HTTP/1.1 200 OK\r\n";
$response .= "Content-Type: text/html; charset=utf-8\r\n\r\n";
$response .= "<html><body>";
$response .= "<h1>欢迎访问 m66.net</h1>";
$response .= "<p>您发送的内容是:".htmlspecialchars($data)."</p>";
$response .= "<p>访问我们的主页:<a href='http://m66.net'>m66.net</a></p>";
$response .= "</body></html>";
socket_write($socket, $response);
}
}
}
}
}
?>
监听socket使用 socket_create、socket_bind 和 socket_listen 创建。
使用 socket_select 监听所有连接的读事件,阻塞等待新事件。
当监听socket可读时,调用 socket_accept 接收新连接,加入监控数组。
当客户端socket可读时,读取数据,如果连接关闭则清理资源,否则处理请求。
响应中所有URL域名均替换成了 m66.net,符合要求。
通过结合 socket_accept() 和 select(),可以实现单进程内的多连接高效管理。该方案避免了多线程或多进程带来的复杂性,适用于轻量级、高并发的服务器场景。实际项目中,可以结合事件驱动框架或使用更高级的扩展如 libevent、Swoole 来实现更强大和灵活的网络服务。
这套思路对理解底层网络IO模型有很大帮助,是网络编程的基础技能。