stream_socket_server 不适合直接做长连接服务,因其仅创建监听 socket,不维护连接状态、不处理粘包/心跳/超时,需手动实现 stream_select 多路复用、连接管理及 EOF 判断等全套逻辑。

stream_socket_server 为什么不适合直接做长连接服务
它本身不维护连接状态,也不处理粘包、心跳、超时断开这些长连接必备逻辑。你用 stream_socket_server 启一个 TCP 监听,只是拿到了一个 socket 资源,后续所有客户端读写、连接管理、多路复用都得自己硬扛——这不是“搭建服务”,是重造轮子的起点。
必须搭配 stream_select 才能真正收发数据
PHP 的 stream_socket_server 默认阻塞,不加控制就卡死在 stream_socket_accept 或 fread 上。真实可用的简易长连接服务,至少得用 stream_select 做 I/O 多路复用:
-
stream_select能同时监听多个 socket(包括 server socket 和已建立的 client socket),避免单连接阻塞整个服务 - 必须把 server socket 加入
$read数组,否则新连接永远收不到 - 每次
stream_select返回后,要遍历所有就绪 socket:是新连接就stream_socket_accept,是已有连接就fread判断是否 EOF(客户端断开) - 注意
stream_select第二个参数(写就绪)和第三个参数(异常就绪)通常不用,但传参不能为null,得传引用变量
客户端断开时 fread 返回空字符串不是 bug
这是 PHP 流的正常行为:fread($client, 1024) 在对端关闭连接后返回 ''(空字符串),不是 false,也不是抛错。很多人误判为“没数据”继续循环,结果 CPU 100% 空转。
- 必须显式检查
fread返回值 ==='',然后fclose($client)并从管理数组中移除该 socket - 别依赖
feof($client),它在某些网络条件下不可靠,尤其有缓冲或半关闭时 - 如果想支持心跳,得自己实现定时检测:记录每个 client 的最后通信时间,每次
fread后更新,stream_select超时后遍历清理超时 socket
bind 失败、Address already in use 怎么快速排障
常见于反复启停调试时,端口没释放干净,或者上一次进程没退出。直接报错:Unable to bind to address [tcp://0.0.0.0:8080]: Address already in use。
立即学习“PHP免费学习笔记(深入)”;
- 先用
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows)查 PID - 确认不是其他服务占用了端口;如果是自己的残留进程,kill 掉
- 启动前加
stream_context_set_option($context, 'socket', 'so_reuseaddr', 1),允许端口快速复用(但仅限开发环境,生产慎用) - 别用 root 启动监听 1024 以下端口,PHP 进程权限不足会静默失败



















