开发PHP简易聊天时,多客户端能否同时接收同一PHP进程经SSE/AJAX发送的数据?
你现在遇到的「每个SSE连接独立运行一次PHP脚本、状态完全隔离」,是PHP传统Web运行模式(PHP-FPM/Apache mod_php)的固有特性,不是代码逻辑写错了。
PHP传统模式的设计逻辑就是「请求进来 → 分配一个独立工作进程 → 跑完请求逻辑 → 销毁进程释放资源」,天生没有跨进程的内存共享、广播能力。你原来的代码里每个连接自己存$number变量从0开始数,哪怕100个客户端连进来,100个进程各自维护各自的计数,不仅两个客户端看到的数字根本不同步,每个进程还占一份系统资源,连接数多了自然会把服务器打满。
要实现“所有客户端接收同源推送的同步内容”,不需要硬让所有客户端挤在一个PHP进程里跑,只要把「全局状态生成」和「客户端消息推送」拆离,用PHP本身就支持的本地文件做跨进程的公共存储层,就能达到和单进程推送完全一致的效果,全程不需要装Redis、队列、WebSocket服务这类额外软件。
实现逻辑分两部分:
- 单独跑一个CLI模式的PHP脚本作为唯一的状态源头,负责持续更新全局数字,把最新状态写到共享文件里,这个脚本只需要启动一次,永久在后台运行。
- 原来的SSE接口不再自己维护状态,每个连接只需要从共享文件里读最新的全局状态,推给当前连接的客户端。不管多少个客户端连进来,读的都是同一个文件的内容,看到的数字完全同步,效果上和单进程推送没有区别。
具体代码实现
后台常驻的状态生成脚本
state_generator.php<?php // 仅支持CLI模式运行,不要通过web访问 $sharedFile = __DIR__ . '/shared_state.txt'; $number = 0; while(true) { $fp = fopen($sharedFile, 'c+'); // 加排他锁避免读写冲突 if(flock($fp, LOCK_EX)) { ftruncate($fp, 0); fwrite($fp, $number); flock($fp, LOCK_UN); } fclose($fp); $number++; sleep(1); }启动方式也不需要额外软件,用Linux系统自带的
nohup命令就能后台挂起:nohup php state_generator.php > /dev/null 2>&1 &修改后的SSE推送接口
myFile.php<?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $sharedFile = __DIR__ . '/shared_state.txt'; // 取消脚本执行时间限制 set_time_limit(0); while(true) { $number = 0; // 读共享文件的最新状态 if(file_exists($sharedFile)) { $fp = fopen($sharedFile, 'r'); // 加共享锁保证读到完整内容 if(flock($fp, LOCK_SH)) { $number = intval(fread($fp, 1024)); flock($fp, LOCK_UN); } fclose($fp); } echo "data: $number\n\n"; @ob_flush(); flush(); sleep(1); }你原来的
client.html完全不需要改动,直接打开多个页面测试,就能看到所有页面的数字是同步递增的,和期望的统一推送效果一致。
方案注意事项
- 这个方案本质上还是每个SSE连接占一个PHP-FPM的工作进程,所以需要把PHP-FPM的
pm.max_children参数调高,适配长连接的数量,不然连接数超过进程池上限就会无法响应。 - 高并发下文件读写的性能会有瓶颈,适合你一开始想做的小规模简易聊天场景。
如果后续连接数上来,想要完全实现「单个PHP进程挂所有客户端连接、统一推送,不占用PHP-FPM的进程资源」的效果,只需要用Workerman即可:这个框架是纯PHP编写的常驻进程库,不需要安装任何系统级服务,通过Composer安装后,就能在单个PHP进程里同时监听所有客户端的SSE连接,把所有连接对象存在进程内存里,后台定时器统一生成消息,遍历所有连接推送给客户端,不管10个还是1000个客户端连接,都只占一个PHP进程的资源,资源占用比之前的文件共享方案低很多。
这个方案的核心是跳出PHP传统的web请求模型,用PHP的网络库自己实现HTTP服务能力,不需要依赖Nginx/Apache的转发,自然也就没有每个连接分配一个PHP-FPM进程的问题。
内容的提问来源于stack exchange,提问作者Guillermo P

