登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP stream_select 怎样实现多个 socket 的可读等待

来源:17golang原创

时间:2026-11-14 05:28:44 104浏览 收藏

在 PHP 里同时等待多个 socket,核心不是给每个连接开一个阻塞循环,而是维护一个“待读集合”,交给 stream_select() 等待。它会修改传入的数组,只留下本轮可以读取、读取到 EOF,或不会阻塞的 stream。因此每次调用前都要从完整集合复制一份,并用 false、0 和正数分别处理失败、超时和事件。

下面的示例只覆盖 TCP 监听 socket、多个客户端的可读等待、超时和连接关闭;写集合、带外数据和完整协议解析不在本文范围内。

官方参考:https://www.php.net/manual/en/function.stream-select.php

可维护的写法是:监听 socket 和客户端 socket 共用一个读集合;调用 stream_select() 前复制集合;返回后只遍历就绪数组;读取到空字符串且 feof() 为真时关闭并移除连接。

读集合才是事件循环的状态

stream_select() 的前三个参数是按引用传递的。调用返回后,$read 不再是“所有被监控的 socket”,而是“本轮已准备好读取的 socket”。如果直接把它当作长期状态,下一轮就会悄悄丢掉没有事件的连接。

因此可以保留一个完整的 $watch,每轮创建 $read = $watch。监听 socket 可读意味着有新连接,客户端 socket 可读则可能是数据,也可能是对端已经关闭连接。

stream_select 读集合与就绪集合的关系说明图
图1:结构说明图,完整监控集合经过 stream_select 后形成当前就绪集合。

用超时把等待交给内核

下面的事件循环把超时设为 2 秒。$seconds 与 $microseconds 共同组成最长等待时间;返回 0 只表示这段时间内没有事件,不代表错误。不要在高频循环里持续使用 0 秒轮询,否则脚本会在没有网络事件时消耗大量 CPU。

这里故意使用 === false 判断失败,因为返回 0 是合法的超时结果。另一个容易忽略的点是:就绪只保证“读取不会立即阻塞”,不保证一次 fread() 就拿到完整消息,所以应用协议仍需自己维护缓冲区。

监听 socket、客户端 socket 和 EOF 要分开处理

监听 socket 和已连接 socket 都能出现在 $read,但含义不同。前者需要 stream_socket_accept(),后者需要读取业务数据。若把监听 socket 当普通客户端读取,事件循环会失去接收新连接的入口。

客户端读取到空字符串时不要立刻关闭:非阻塞读取下,先用 feof() 判断是否真的到达 EOF。只有“空字符串且 EOF”为真,才从 $watch 删除并调用 fclose()。否则连接会被误判为断开,或者继续把无效资源放在集合里。

stream_select 数据可读与 EOF 关闭边界说明图
图2:结构说明图,客户端可读事件需要沿着数据分支或 EOF 关闭分支处理。

三个边界决定实现是否稳定

返回或状态正确含义处理建议
false调用失败,可能被信号中断记录上下文并进入错误处理,不当成超时
0在上限时间内没有事件执行轻量维护,再进入下一轮
正数至少一个 stream 就绪只遍历被改写后的数组
空字符串且 feof()对端已关闭连接移除监控项并关闭资源

如果运行在 Windows,还要留意手册对 proc_open() 文件描述符和控制台 STDIN 的兼容性说明;这类输入不应未经验证就与 TCP stream 混用。stream_select() 也不是所有 stream 都支持,zlib 等特殊流不能直接按 socket 方式选择。

常见问题

为什么不能把 null 直接传给 stream_select() 的第三个参数?

因为相关参数按引用传递,某些 PHP 版本不能把常量直接作为引用参数。使用示例中的临时变量 $except = null 更稳妥。

stream_select() 返回可读后一定能读到完整消息吗?

不一定。它只说明当前读取不会立即阻塞,可能只读到一部分字节;应用应把数据放入连接级缓冲区,再按协议边界组装消息。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>