reactphp在php 8.3中仍基于事件循环与回调驱动,不依赖fiber或协程语法糖;需手动设置流为非阻塞、显式调用$loop->run()、正确处理eof与eagain错误,并警惕opcache预加载导致的loop实例异常。

stream_select()(或 ext-ev/ext-uv 等扩展)轮询文件描述符状态,真正实现零阻塞。
下面直说关键点怎么落地:
addReadStream / addWriteStream 必须配非阻塞流
这是最容易翻车的地方:ReactPHP 的 addReadStream() 和 addWriteStream() 不会帮你设非阻塞模式,它只监听已就绪的 fd。
- 如果流是阻塞的(比如
fopen('php://stdin', 'r')在 Windows 上),addReadStream()注册后回调可能永不触发,或首次读就卡死 - 必须手动调用
stream_set_blocking($stream, false),且要在addReadStream()前执行 - 常见失败场景:
fopen('php://stdin', 'r')在 Windows 下无法设为非阻塞 → 改用STDIN资源并显式设置,或换 Linux 环境测试 - 网络流(如
stream_socket_client())创建后默认阻塞,也得立刻stream_set_blocking($socket, false)
HTTP 客户端并发请求别漏掉 $loop->run()
ReactPHP 的 Browser 是基于 Promise 的,但 Promise 不会自动驱动事件循环 —— 它只是注册回调,真正调度靠 $loop->run()。
- 漏掉这句,所有
$browser->get()都不会发出请求,程序直接退出 - 不要在 CLI 脚本末尾加
exit()或提前 return,否则$loop->run()没机会执行 - 如果你用的是
React\EventLoop\Loop::get(),确保没被多次调用导致 loop 实例错乱(推荐统一用Factory::create()显式创建)
错误处理必须覆盖 stream EOF 和 write EAGAIN
非阻塞流读写失败不是异常,而是返回 false 或空字符串,但原因不同:
-
fgets($stream) === false可能是流关闭(EOF),也可能是暂时无数据(EAGAIN)→ 此时不能移除监听,应继续等待 -
fwrite($stream, $data) === false通常意味着缓冲区满或连接断开;若返回 0,大概率是 EAGAIN,需等addWriteStream()下次触发 - 建议用
stream_get_meta_data($stream)['eof']判断是否真 EOF,再决定是否$loop->removeReadStream($stream) - 对 socket 写失败,可结合
socket_get_status($stream)查['blocked']和['timed_out']字段辅助判断
opcache.preload,而 preload 脚本里提前 require 了 ReactPHP 类但没初始化 loop,可能导致后续 Loop::get() 返回空实例 —— 这种问题只在生产环境复现,本地 CLI 跑不出。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











