当前位置: 首页 > news >正文

php把运行时重构成常驻内存 + 多进程 + 事件驱动(Reactor) 模式完整流程=workerman

纯手写版,不靠 Workerman/Swoole。 只用 PHP 自带能力:pcntl_fork + stream_socket_server + stream_select,实现你要的: 常驻内存 + 多进程 + Reactor 事件驱动 --- 一、先说目标(大白话) 把 PHP 从“脚本模式”改成“服务模式”: - 脚本模式:请求来一次,PHP 启一次,用完就死 - 服务模式:进程长期活着,反复处理请求 并发靠两层:1. 多进程:主进程 fork 多个 worker,吃多核2. 事件驱动:每个 worker 用 stream_select 盯很多连接,谁有数据就处理谁 --- 二、目录结构 mini-reactor/ ├── server.php └── src/ ├── Master.php ├── Worker.php ├── Reactor.php ├── Http.php └── App.php --- 三、完整代码(可直接跑)1)src/Reactor.php<?php namespace MiniReactor;class Reactor{private array$readStreams=[];private array$readHandlers=[];private bool$running=true;publicfunctionadd($stream, callable$handler): void{$id=(int)$stream;stream_set_blocking($stream,false);$this->readStreams[$id]=$stream;$this->readHandlers[$id]=$handler;}publicfunctiondel($stream): void{$id=(int)$stream;unset($this->readStreams[$id],$this->readHandlers[$id]);}publicfunctionstop(): void{$this->running=false;}publicfunctionrun(): void{while($this->running){if(!$this->readStreams){usleep(10000);continue;}$read=array_values($this->readStreams);$write=[];$except=[];$n=@stream_select($read,$write,$except,1);if($n===false||$n===0){continue;}foreach($readas$stream){$id=(int)$stream;if(isset($this->readHandlers[$id])){($this->readHandlers[$id])($stream,$this);}}}}}---2)src/Http.php<?php namespace MiniReactor;class HttpRequest{public string$method='GET';public string$path='/';public array$query=[];public array$headers=[];public string$body='';}class Http{public staticfunctionparse(string$raw): ?HttpRequest{$parts=explode("\r\n\r\n",$raw,2);$head=$parts[0]??'';$body=$parts[1]??'';$lines=explode("\r\n",$head);if(count($lines)<1)returnnull;$start=array_shift($lines);if(!preg_match('#^([A-Z]+)\s+(\S+)\s+HTTP/1\.[01]$#',$start,$m)){returnnull;}$req=new HttpRequest();$req->method=$m[1];$uri=$m[2];$req->path=parse_url($uri, PHP_URL_PATH)?:'/';parse_str(parse_url($uri, PHP_URL_QUERY)?:'',$req->query);foreach($linesas$line){if(!str_contains($line,':'))continue;[$k,$v]=explode(':',$line,2);$req->headers[strtolower(trim($k))]=trim($v);}$req->body=$body;return$req;}public staticfunctionresponse(int$status, array$data): string{$reason=match($status){200=>'OK',400=>'Bad Request',404=>'Not Found',500=>'Internal Server Error', default=>'OK',};$json=json_encode($data, JSON_UNESCAPED_UNICODE);$headers=["HTTP/1.1 {$status} {$reason}","Content-Type: application/json; charset=utf-8","Content-Length: ".strlen($json),"Connection: close","",$json];returnimplode("\r\n",$headers);}}---3)src/App.php<?php namespace MiniReactor;class App{private int$bootAt;private int$counter=0;// 常驻内存状态:这个worker处理了多少请求 publicfunction__construct(){$this->bootAt=time();}publicfunctionhandle(HttpRequest$req): array{$this->counter++;if($req->path==='/ping'){return['status'=>200,'body'=>['code'=>0,'msg'=>'pong','pid'=>getmypid(),'uptime_sec'=>time()-$this->bootAt,'handled'=>$this->counter,]];}if($req->path==='/hello'){$name=$req->query['name']??'world';return['status'=>200,'body'=>['code'=>0,'data'=>"hello {$name}",'pid'=>getmypid(),'handled'=>$this->counter,]];}return['status'=>404,'body'=>['code'=>404,'msg'=>'not found','pid'=>getmypid(),'handled'=>$this->counter,]];}}---4)src/Worker.php<?php namespace MiniReactor;class Worker{private$listenSocket;private Reactor$reactor;private App$app;private bool$stopping=false;publicfunction__construct($listenSocket){$this->listenSocket=$listenSocket;$this->reactor=new Reactor();$this->app=new App();// 常驻内存对象,只初始化一次}publicfunctionrun(): void{pcntl_async_signals(true);pcntl_signal(SIGTERM,function(){$this->stopping=true;$this->reactor->stop();});pcntl_signal(SIGINT,function(){$this->stopping=true;$this->reactor->stop();});// 监听“有新连接”$this->reactor->add($this->listenSocket,function($serverSock, Reactor$reactor){$conn=@stream_socket_accept($serverSock,0);if(!$conn)return;stream_set_blocking($conn,false);$buffer='';// 监听“这个连接上有数据可读”$reactor->add($conn,function($client, Reactor$reactor)use(&$buffer){$chunk=@fread($client,8192);if($chunk===''||$chunk===false){$reactor->del($client);@fclose($client);return;}$buffer.=$chunk;// 简化版:读到头部结束就处理(演示够用)if(!str_contains($buffer,"\r\n\r\n")){return;}$req=Http::parse($buffer);if(!$req){@fwrite($client, Http::response(400,['code'=>400,'msg'=>'bad request']));$reactor->del($client);@fclose($client);return;}try{$ret=$this->app->handle($req);$raw=Http::response($ret['status'],$ret['body']);}catch(\Throwable$e){$raw=Http::response(500,['code'=>500,'msg'=>'internal error']);}@fwrite($client,$raw);$reactor->del($client);@fclose($client);});});echo"[worker ".getmypid()."] started\n";$this->reactor->run();echo"[worker ".getmypid()."] stopped\n";}}---5)src/Master.php<?php namespace MiniReactor;class Master{private string$host;private int$port;private int$workerNum;private$listenSocket;private array$workers=[];private bool$stopping=false;publicfunction__construct(string$host, int$port, int$workerNum){$this->host=$host;$this->port=$port;$this->workerNum=$workerNum;}publicfunctionstart(): void{$errno=0;$errstr='';$context=stream_context_create(['socket'=>['so_reuseport'=>1,'backlog'=>1024,]]);$this->listenSocket=stream_socket_server("tcp://{$this->host}:{$this->port}",$errno,$errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,$context);if(!$this->listenSocket){throw new\RuntimeException("listen failed: {$errstr}({$errno})");}stream_set_blocking($this->listenSocket,false);pcntl_async_signals(true);pcntl_signal(SIGINT, fn()=>$this->gracefulStop());pcntl_signal(SIGTERM, fn()=>$this->gracefulStop());pcntl_signal(SIGCHLD, fn()=>$this->reap());echo"[master ".getmypid()."] listen {$this->host}:{$this->port}, workers={$this->workerNum}\n";for($i=0;$i<$this->workerNum;$i++){$this->forkWorker();}while(!$this->stopping){sleep(1);}$this->shutdownAll();}privatefunctionforkWorker(): void{$pid=pcntl_fork();if($pid<0){throw new\RuntimeException("fork failed");}if($pid===0){$worker=new Worker($this->listenSocket);$worker->run();exit(0);}$this->workers[$pid]=true;echo"[master] fork worker {$pid}\n";}privatefunctionreap(): void{while(($pid=pcntl_waitpid(-1,$status,WNOHANG))>0){unset($this->workers[$pid]);echo"[master] worker {$pid} exited\n";if(!$this->stopping){$this->forkWorker();// 自动补齐}}}privatefunctiongracefulStop(): void{$this->stopping=true;echo"[master] stopping...\n";}privatefunctionshutdownAll(): void{foreach(array_keys($this->workers)as$pid){posix_kill($pid, SIGTERM);}while(count($this->workers)>0){$this->reap();usleep(100000);}if(is_resource($this->listenSocket)){fclose($this->listenSocket);}echo"[master] shutdown done\n";}}---6)server.php(启动入口)<?php require_once __DIR__.'/src/Reactor.php';require_once __DIR__.'/src/Http.php';require_once __DIR__.'/src/App.php';require_once __DIR__.'/src/Worker.php';require_once __DIR__.'/src/Master.php';use MiniReactor\Master;$host='0.0.0.0';$port=8080;$cpu=(int)trim(shell_exec('nproc 2>/dev/null')?:'4');$workerNum=max(2,$cpu);$master=new Master($host,$port,$workerNum);$master->start();--- 四、怎么启动 php server.php 测试:curl"http://127.0.0.1:8080/ping"curl"http://127.0.0.1:8080/hello?name=jack"你会看到 pid 变化,说明请求被不同 worker 处理。 --- 五、完整流程(超白话)1. Master 启动并监听端口2. Master fork 出多个 Worker3. 每个 Worker 创建一个 Reactor 循环并常驻4. 有新连接时,Reactor 收到“可读事件”5. accept 连接,再监听这个连接的读事件6. 读到 HTTP 请求,交给 App 处理7. 写回响应,关闭连接8. 回到 Reactor 等下一波事件9. 某个 Worker 挂了,Master 自动补一个 --- 六、这版是“自己写”的价值和边界 价值 - 你能完全理解底层模型,不黑箱 - 任何框架(Workerman/Swoole)本质都能看懂 边界 这份代码是教学可运行版,不是完整生产HTTP服务器。生产还要补: - Keep-Alive、分块传输、大包拆包、超时 - 连接限流、日志、监控、热重启 - 真正数据库连接池与慢查询保护 --- 一句话收尾: 这份代码已经是纯手写的 PHP 常驻内存 + 多进程 + Reactor 最小完整实现,你可以直接在这个骨架上继续加协议细节和业务能力。
http://www.jsqmd.com/news/746069/

相关文章:

  • WinAppDriver环境搭建避坑大全:解决.NET依赖、版本冲突和‘找不到元素’的常见问题
  • Python模型配置统一管理方案(企业级配置中心落地全图谱)
  • STM32内核精讲 | 第四章 指令集基础 —— Thumb® 与 Thumb‑2
  • 拼多多以“技术驱动效率革命“为核心战略,聚焦供应链数字化与智能化升级
  • 通过curl命令直接测试Taotoken大模型API接口
  • ComfyUI-WanVideoWrapper深度解析:企业级AI视频生成架构与性能优化实战指南
  • 百度文库文档打印助手:5分钟掌握纯净文档获取技巧
  • 构建多 Agent 协作系统时如何通过 Taotoken 统一管理模型调用
  • 基于TMS320F28027的智能小车开发(一):电机PWM驱动模块详解与避坑指南(附b站视频教程)
  • 告别风扇噪音与高温:FanControl让你的PC散热更智能
  • 某音a_bogus vmp逆向
  • 【2026年最新版】收藏备用!小白程序员必学的LLM智能体入门指南(从基础到实操)
  • Appium Inspector进阶玩法:除了看元素,这些隐藏功能让你的测试效率翻倍
  • OpenClaw从入门到应用——Agent:流式传输与分块
  • Fairseq-Dense-13B-Janeway保姆级教学:从显存监控(nvidia-smi)到生成质量评估全流程
  • 将 Hermes Agent 工具链接入 Taotoken 的统一模型平台
  • 开源本地化入门:从Presentify项目学习软件国际化与GitHub协作
  • 企业网里给奇安信天眼‘安家’:探针镜像口配置与网络规划的那些事儿
  • STM32开发工具
  • Octogen:让AI代理原生操作数据库,实现自然语言数据查询与分析
  • Clawtique:OpenClaw的模块化能力管理器,解决插件污染与依赖难题
  • 点云配准对不齐、ICP收敛失败、法线估计飘移——Python 3D调试7大暗坑全图谱(含Jupyter交互式诊断工具包)
  • Claude学习笔记【第三章】- Claude Code的基本使用
  • Face Analysis WebUI实战教程:结合Pillow实现检测结果图自动裁剪保存
  • 怎么修复qt5core.dll【图文讲解】qt5core.dll 丢失?如何修复dll?dll文件缺失?qt5core.dll 无法继续执行代码?4种方法一键修复
  • 使用 curl 命令直接测试 Taotoken 大模型 API 的连通性与响应
  • TiViBench:视频生成模型的视觉推理评估系统
  • 支持实时滤波--IIR巴特沃斯低通滤波器(数字滤波器)
  • GitHub Copilot在IDEA/VSCode里的10个高效用法:不止是代码补全,还能写测试和文档
  • 电力设备红外图像与可见光图像配准数据集205对共410张图无标注