
PHP 8.6 的 Io\Poll API 让 PHP 引擎第一次原生支持 epoll/kqueue,无需任何 C 扩展。本文介绍这个新 API、基于它(及 Symfony polyfill)编写的 Workerman 事件循环驱动,以及安装使用方法与性能实测。
01 PHP 8.6 的新 I/O API:Io\Poll
1.1 它解决什么问题
长期以来,PHP 想做高性能网络服务只有两条路:
stream_select():内置但能力有限——fd 上限约 1024(FD_SETSIZE),每轮全量扫描,O(n) 复杂度;PHP 8.6 合入了 Poll API RFC,把 epoll(Linux)、kqueue(macOS/BSD)、WSAPoll(Windows)直接做进了引擎:
特性 | 说明 |
|---|---|
零扩展依赖 | 引擎内置,composer 生态直接可用 |
标准多路复用 | 就绪事件通知,O(1) 获取就绪 fd |
无 fd 上限 | 摆脱 FD_SETSIZE 1024 限制 |
与 Fiber 配合 | 未来异步框架的原生底座 |
1.2 API 长什么样
核心概念有四个:
概念 | 作用 |
|---|---|
Io\Poll\Context | 轮询上下文,注册/监视多个句柄,支持 Read、Write、Error、Hang-up 事件 |
Io\Poll\Event | 事件类型枚举,如 Event::Read |
Watcher | wait() 返回的就绪对象,可携带自定义应用数据,可动态修改或移除 |
StreamPollHandle | 把 PHP 原生流资源包装成 Poll 可识别的句柄 |
看一个官方风格的 TCP 服务器示例:
use Io\Poll\Context;
use Io\Poll\Event;
use StreamPollHandle;
$server = stream_socket_server('tcp://127.0.0.1:8080');
$poll = new Context();
poll->add(new StreamPollHandle(
// 阻塞等待事件(超时 30 秒),只返回就绪的 watcher
foreach (poll->wait(timeoutSeconds: 30) as
if ($watcher->hasTriggered(Event::Read)) {
connection = stream_socket_accept(watcher->getHandle()->getStream());
// 处理新连接...
}
}
对比 stream_select(),最大的区别是状态化的:fd 注册一次,之后每轮 wait() 只返回就绪者;而 stream_select 每轮都要把所有 fd 集合重新传进去、全量比对,还要自己维护几组平行数组。
1.3 Symfony polyfill:PHP 8.1 就能用
Symfony 团队在 1.41.0 版本同步发布了 symfony/polyfill-io-poll,把这个 API 反向移植到 PHP 8.1+。公告里一语道破了它的价值:
◆ Symfony 官方公告
以往使用 stream_select() 时,异步框架和应用必须管理复杂的平行数组,并处理各种平台限制。Io\Poll 彻底解决了这一痛点,让开发者能够在旧版 PHP 中提前写出更现代、更易维护的异步网络代码。
运行机制:
两个注意点(同样来自公告与实测):
wait() 参数风格略有差异(整数秒 vs Time\Duration),上游正在统一,跨版本代码需要稍作兼容(本文的驱动已处理)。这意味着:同一份代码,升级 PHP 版本就自动获得 epoll 级性能,零改动。这就是我为 Workerman 写这个驱动的原因。
02 Workerman 的事件循环是怎么插拔的
Workerman 5.x 把所有 I/O 多路复用抽象在 Workerman\Events\EventInterface 接口里,内置了 Select、Ev、Event、Swoole、Swow 等驱动。切换驱动只需一行:
Worker::$eventLoopClass = \Workerman\Events\Select::class;
接口方法一览(就是驱动要实现的全部):
方法 | 作用 |
|---|---|
onReadable / offReadable | 注册/取消流可读回调 |
onWritable / offWritable | 注册/取消流可写回调 |
delay / offDelay | 一次性定时器 |
repeat / offRepeat | 重复定时器 |
onSignal / offSignal | 信号处理 |
run / stop | 启动/停止事件循环 |
03 安装与使用
3.1 安装
composer require tinywan/workerman-event-poll
要求:
环境 | 依赖 |
|---|---|
PHP 8.6+ | 无(引擎内置 Io\Poll) |
PHP 8.1 ~ 8.5 | symfony/polyfill-io-poll(自动安装) |
POSIX 系统 | ext-pcntl、ext-posix(Workerman 本身的要求) |
3.2 使用
<?php
require __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Events\Poll;
Worker::$eventLoopClass = Poll::class;
$worker = new Worker('http://0.0.0.0:8080');
worker->onMessage = function (
$connection->send('hello');
};
Worker::runAll();
启动后执行 php start.php status,event-loop 列显示 poll 即生效:
php server.php start Workerman[server.php] start in DEBUG mode --------------------------------------------- WORKERMAN --------------------------------------------- Workerman/5.2.2 PHP/8.4.15 (JIT off) Linux/6.18.33.2-microsoft-standard-WSL2 ---------------------------------------------- WORKERS ---------------------------------------------- event-loop proto user worker listen count state poll tcp root poll-tcp tcp://0.0.0.0:2345 1 [OK] poll tcp root poll-http http://0.0.0.0:8401 1 [OK] ----------------------------------------------------------------------------------------------------- Press Ctrl+C to stop. Start success.TCP、WebSocket、定时器、信号全部照常工作,业务代码不需要任何改动——这就是"drop-in replacement"的意义。
3.3 自动兑现验证
同一份代码在两个 PHP 版本下的实际运行效果:
运行时 | 底层实现 | 结果 |
|---|---|---|
PHP 8.4 + polyfill | stream_select(polyfill 内部) | TCP/HTTP/定时器全部通过 |
PHP 8.6.0beta1 | 引擎原生 epoll | 同上,零改动 |
04 性能实测(wrk 压测摘要)
单进程、wrk -t2 -c200 -d20s、最小 HTTP 响应:
配置 | RPS | 说明 |
|---|---|---|
PHP 8.6 + Poll(原生 epoll) | ~51,500 | 全场第一 |
Swoole 原生(关协程,参照) | ~42,000 | C 扩展引擎 |
PHP 8.4 + Poll(polyfill) | ~25,750 | 比内置 Select 快约 15% |
PHP 8.4 + Select(内置) | ~22,450 | Workerman 默认回退 |
4 worker 多进程:8.6 原生 Poll ~119k,仍是第一;polyfill 阶段(~71k)则不及 Select(~99k)——polyfill 是纯 PHP 实现,它的价值在 API 兼容与升级路径,8.1~8.5 追求多进程极限吞吐建议继续用 Select/Ev/Event。完整数据与复现脚本见仓库 BENCHMARK.md 和 examples/bench.sh。
05 总结
问题 | 答案 |
|---|---|
这个驱动现在有什么用? | PHP 8.1~8.5 单进程场景比默认 Select 快 15%,且无 fd 上限焦虑 |
真正的价值? | 零改动升级路径:升 PHP 8.6 自动切换引擎级 epoll,吞吐翻倍 |
为什么不等 8.6 再写? | 驱动 API 现在就固定下来,8.6 到来时业务代码一行不用改 |
项目地址:github.com/Tinywan/workerman-event-Poll,MIT 协议,欢迎 Star、Issue 和 PR。
相关链接
▸ PHP RFC: Poll API
wiki.php.net/rfc/poll_api
▸ Symfony Polyfill 1.41.0 发布公告
symfony.com/blog/symfony-polyfill-1-41-0-released-io-poll-now-available
▸ symfony/polyfill 仓库
github.com/symfony/polyfill
▸ 本项目仓库
github.com/Tinywan/workerman-event-Poll
— END —
觉得有用的话,欢迎点赞、在看、转发, 你的支持是我继续折腾 PHP 新特性的动力。