本文目录导读:

在 PHP 中实现多路复用(I/O Multiplexing),主要目的是让一个进程/线程同时处理多个连接(如 WebSocket、TCP 连接),提高并发性能,减少资源消耗。
核心思想:不等待单个连接的数据,而是同时监控多个连接,哪个连接有数据到达就处理哪个。
以下是几种常见的 PHP 多路复用实现方式:
基础:使用 stream_select() 函数
这是 PHP 自带的、最底层的多路复用实现,它直接对应 Unix 系统调用的 select。
原理:传入一组 socket 流(资源),调用 stream_select 阻塞等待,当有任意一个 socket 可读/可写时,函数返回,然后你遍历返回的数组处理。
适用场景:简单的聊天服务器、自定义 TCP 服务器。
案例:一个简单的 TCP Echo 服务器
<?php
// 创建监听 socket
$server = stream_socket_server("tcp://0.0.0.0:8080", $errno, $errstr);
if (!$server) {
die("服务器创建失败: $errstr ($errno)");
}
// 保存所有客户端连接的数组
$clients = [$server];
echo "服务器启动,监听 0.0.0.0:8080...\n";
while (true) {
// 1. 复制一份数组,select 会修改它们
$read = $clients;
$write = [];
$except = [];
// 2. 阻塞等待,直到有 socket 可读
// 参数:监控数组,超时时间(null 表示无限等待)
if (stream_select($read, $write, $except, 0) === false) {
// 被信号中断或出错,继续
continue;
}
// 3. 处理有事件的 socket
foreach ($read as $socket) {
if ($socket === $server) {
// 有新连接接入
$newClient = stream_socket_accept($server);
$clients[] = $newClient;
echo "新客户端连接\n";
} else {
// 已有客户端发来数据
$data = fread($socket, 1024);
if ($data === false || $data === '') {
// 客户端断开连接
fclose($socket);
$key = array_search($socket, $clients);
unset($clients[$key]);
echo "客户端断开\n";
} else {
// 处理数据(这里原样返回)
fwrite($socket, "收到: " . $data);
}
}
}
}
运行上述代码后,你可以用 telnet 127.0.0.1 8080 或 nc 127.0.0.1 8080 多开几个终端测试。
缺点:
- 性能受限于
select的最大文件描述符数(1024)。 - 需要自行管理连接数组和事件循环。
进阶:使用 PHP 扩展(推荐)
PHP 官方提供了两个强大的扩展来实现高效多路复用:
pcntl:用于进程控制(多进程/多 worker)。event:基于 libevent 库(高性能事件通知机制)。swoole/workerman:第三方的生产级网络框架,内部封装了多路复用。
A. 使用 event 扩展
基于 libevent,效率比 stream_select 高很多,支持 epoll(Linux)、kqueue(macOS)、IOCP(Windows)。
安装:pecl install event (需要 libevent-dev)
原理:创建事件循环(EventBase),把 socket 和回调函数绑定,当 socket 有数据时,libevent 自动触发回调。
示例:
<?php
// 创建监听 socket
$server = stream_socket_server('tcp://0.0.0.0:8080');
stream_set_blocking($server, false); // 非阻塞模式
$base = new EventBase();
$event = new Event($base, $server, Event::READ | Event::PERSIST, function ($server) use ($base) {
$conn = @stream_socket_accept($server);
if ($conn) {
stream_set_blocking($conn, false);
// 新连接事件
$event = new Event($base, $conn, Event::READ | Event::PERSIST, function ($conn) {
$data = fread($conn, 1024);
if ($data === '' || $data === false) {
fclose($conn);
} else {
fwrite($conn, "收到: $data");
}
});
$event->add();
}
});
$event->add();
$base->loop(); // 启动事件循环
优点:高效跨平台,适合生产环境。 缺点:需要安装扩展。
生产首选:使用 Swoole / Workerman
除非你有非常底层的需求,不然直接用这些框架是最佳实践,它们把多路复用、进程管理、协程等细节全部封装好了。
Swoole(推荐,协程支持好)
Swoole 是一个 C 扩展,集成了多路复用(epoll)、协程、TCP/UDP/WebSocket 服务器。
示例:高性能 TCP 服务器
<?php
// 创建 Server 对象,监听 0.0.0.0:8080
$server = new Swoole\Server('0.0.0.0', 8080);
// 监听连接进入事件
$server->on('Connect', function ($server, $fd) {
echo "客户端连接: {$fd}\n";
});
// 监听数据接收事件
$server->on('Receive', function ($server, $fd, $reactor_id, $data) {
$server->send($fd, "收到: {$data}");
});
// 监听连接关闭事件
$server->on('Close', function ($server, $fd) {
echo "客户端断开: {$fd}\n";
});
// 启动服务器(内部会使用 epoll 多路复用)
$server->start();
启动:php server.php (配合 swoole-cli 或 FPM 模式)
Workerman(纯 PHP 实现)
Workerman 是纯 PHP 写的网络框架,通过 stream_select 或 event 扩展实现多路复用,推荐同时安装 event 扩展提升性能。
示例:
<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';
// 创建一个 TCP 服务器
$worker = new Worker('tcp://0.0.0.0:8080');
// 4 个进程
$worker->count = 4;
$worker->onConnect = function ($connection) {
echo "新连接\n";
};
$worker->onMessage = function ($connection, $data) {
$connection->send("收到: {$data}");
};
$worker->onClose = function ($connection) {
echo "断开\n";
};
Worker::runAll();
运行:php start.php start
如何选择?
| 方案 | 场景 | 性能 | 开发成本 | 生产可靠性 |
|---|---|---|---|---|
stream_select |
学习原理、简单原型 | 低 | ||
event 扩展 |
高性能自定义服务 | 中 | ||
| Swoole | 高并发 API、WebSocket、微服务 | 中(需学习) | ||
| Workerman | 中等并发、纯 PHP 环境 | 低(PHP 原生) |
如果你是在做生产项目:
- 首选 Swoole(协程模式,性能天花板最高,社区活跃,文档好)。
- 如果无法安装 C 扩展(例如虚拟主机环境),选择 Workerman。
如果你只是想理解多路复用的原理:
- 用
stream_select写一个简单 demo,理解事件循环模型,再过渡到 Swoole/Workerman。
附:为什么 PHP 要这么麻烦?
对比 Java/Go/Node.js:
- Java 的 NIO/Netty、Go 的 goroutine(GMP 模型)、Node.js 的 libuv 都是原生自带高效多路复用。
- PHP 传统上是 “共享无状态” 的 CGI 模型(每个请求一个进程),处理长连接/高并发需要额外工作。
PHP 的多路复用能力完全够用(Swoole 在同类框架中性能居前),但需要你主动选择相应的工具和框架,如果你是从共享主机的古老 PHP 编程过来,先习惯用 Swoole/Workerman,才算真正进入现代 PHP 网络编程。