【基于 Swoole+Hyperf 的微服务实战】 第一周·周一 理解 Swoole 的核心——Reactor 事件循环

发布时间:2026/8/18 19:29:51
【基于 Swoole+Hyperf 的微服务实战】 第一周·周一 理解 Swoole 的核心——Reactor 事件循环 第一周·周一内容可以照着这个步骤学完一天并在当天结束时完成一个具备并发能力的 TCP 回声服务器同时深刻理解 Swoole 的核心——Reactor 事件循环。今日目标用 Docker 一键搭建 Swoole 5.x 开发环境无需折腾编译。吃透 Reactor 模型理解 Swoole 如何用单进程处理海量并发连接。动手编写一个纯 TCP 回声服务器并观察同步阻塞与协程化的巨大差异。能使用telnet和多个终端窗口对自己的服务器进行并发交互测试。一、环境搭建Docker 与 Swoole 5.x (约 1 小时)我们使用官方镜像省去安装 PHP 和 Swoole 的繁琐过程。1. 准备项目目录在你的工作目录下新建文件夹例如swoole-course后续所有代码都放在这里。mkdirswoole-coursecdswoole-course2. 编写docker-compose.yml这个文件定义了我们的 PHP-Swoole 容器它会挂载当前目录到容器内的/var/www方便我们直接在宿主机写代码容器内运行。version:3.8services:swoole:# 使用官方 Swoole 5.1 PHP 8.2 镜像image:phpswoole/swoole:5.1-php8.2container_name:swoole-labvolumes:# 把当前目录映射到容器的 /var/www-.:/var/wwwworking_dir:/var/wwwports:# 将容器的 9501 端口映射到宿主机的 9501用于后续 TCP 连接-9501:9501# 让容器持续运行不至于立刻退出tty:true3. 启动环境在swoole-course目录下执行docker-composeup-d4. 进入容器并验证 Swoole# 进入容器内的 Bashdocker-composeexecswoolebash# 检查 PHP 版本php-v# 检查 Swoole 扩展及版本php-m|grepswoole php--riswoole当你看到Swoole enabled和Version 5.1.x时环境就 OK 了。退出容器用exit即可但我们下面的操作都在容器内进行。二、知识核心彻底搞懂 Reactor 事件循环 (约 1.5 小时)类比理解餐厅服务员一个传统 PHP-FPM 餐厅里每来一个客人请求老板就临时雇一个服务员进程/线程服务完就解雇。1000 个客人同时来就得雇 1000 个服务员系统负担极重。而 Swoole 的 Reactor 模型就像一个超级服务员。他手里有一个记录板事件循环时刻关注所有客人的桌子连接。客人招手有数据到达时他才过去处理一下然后立刻回来继续看记录板。一个人就能同时服务成百上千个客人。技术原理同步阻塞 (Blocking I/O)传统的socket_read如果没有数据整个进程就卡住傻等什么也做不了。I/O 多路复用 (epoll)操作系统提供一个机制能同时监听成百上千个 socket。Swoole 的事件循环底层就是基于epoll它不断询问操作系统“哪个 socket 有新数据”然后只去处理那些有事件的 socket。Reactor 模式事件循环 (EventLoop)一个无限循环不停地等待事件发生。事件分发事件发生新连接、数据到达、连接关闭循环根据注册好的回调函数去执行相应逻辑。非阻塞回调函数必须快速执行绝对不能有sleep或阻塞的数据库查询否则这个超级服务员就会被卡在那个客人的桌前其他所有客人都被晾着了。这就是今天我们要在代码里亲手验证的道理。三、实战纯 TCP 回声服务器与协程初体验 (约 2 小时)所有代码都在宿主机swoole-course目录下编写然后在容器内执行。1. 基础版单连接阻塞回声服务器创建文件tcp_echo_blocking.php?php// 创建一个 TCP 服务器监听 0.0.0.0:9501$servernewSwoole\Server(0.0.0.0,9501);// 配置参数$server-set([worker_num1,// 为了清晰演示阻塞效果我们只开一个 Worker 进程]);// 监听连接建立事件$server-on(Connect,function($server,$fd){echo新客户端接入连接ID:{$fd}\n;});// 监听数据接收事件$server-on(Receive,function($server,$fd,$reactorId,$data){echo收到来自连接 [{$fd}] 的数据: .trim($data).\n;// 模拟一个耗时业务操作比如查询数据库危险操作echo开始处理耗时任务阻塞5秒...\n;sleep(5);// 这是同步阻塞的睡眠整个进程会卡死// 发送回声$server-send($fd,服务器回复: .$data);});// 监听连接关闭事件$server-on(Close,function($server,$fd){echo连接 [{$fd}] 已关闭\n;});// 启动服务器$server-start();2. 观察基础版的阻塞在容器内启动服务器docker-composeexecswoolebashphp tcp_echo_blocking.php现在打开两个终端窗口都执行telnet localhost 9501。在窗口 1 输入hello1并回车。立刻切换到窗口 2输入hello2并回车。现象窗口 1 会在 5 秒后收到回复。而窗口 2 没有任何反应直到窗口 1 的 5 秒结束后窗口 2 才开始处理并等待 5 秒。结论sleep(5)阻塞了整个 Worker 进程的事件循环导致它无法响应窗口 2 的连接。这就是 “Reactor 模型必须搭配非阻塞代码” 的原因。3. 进阶版协程化的非阻塞回声服务器Swoole 提供了Swoole\Coroutine\System::sleep()来替代sleep()它会让出当前协程的控制权Worker 进程可以立即去处理其他连接的请求。创建文件tcp_echo_coroutine.php?php$servernewSwoole\Server(0.0.0.0,9501);$server-set([worker_num1,// Swoole 5 默认开启协程我们也可以显式开启以确保无误enable_coroutinetrue,]);$server-on(Connect,function($server,$fd){echo新客户端接入连接ID:{$fd}\n;});$server-on(Receive,function($server,$fd,$reactorId,$data){echo收到来自连接 [{$fd}] 的数据: .trim($data).\n;echo开始处理耗时任务协程休眠5秒非阻塞...\n;// 使用协程版本的 sleep让出 CPU事件循环可处理其他请求Swoole\Coroutine\System::sleep(5);$server-send($fd,协程服务器回复: .$data);echo任务完成已回复连接 [{$fd}]\n;});$server-on(Close,function($server,$fd){echo连接 [{$fd}] 已关闭\n;});$server-start();4. 观察非阻塞与并发处理停止上一个tcp_echo_blocking.phpCtrlC运行新的php tcp_echo_coroutine.php同样打开两个终端窗口都用telnet localhost 9501连接。在窗口 1 输入hello1并回车。立刻切换到窗口 2输入hello2并回车。现象你会看到两个窗口几乎同时收到了回声而且都是在 5 秒后。结论通过协程我们让这个单进程的超级服务员同时处理了两个耗时任务完全没有任何阻塞。这就是 Swoole 高性能的核心秘密。四、成果测试与方法检验 (约 1 小时)1. 基础端口连通性测试服务器运行时在宿主机或容器内执行# 检查 9501 端口是否在监听netstat-tulnp|grep9501# 应该看到一条 LISTEN 状态进程为 php2. telnet 并发交互测试这是最直接的测试。分别用几个终端连接发送不同消息检验以下几点每个连接是否能立即收到回声回复内容是否与发送的完全一致如果一个连接断开Ctrl]然后输入quit服务器日志是否打印“已关闭”且不影响其他连接3. 进阶思考与自测任务修改tcp_echo_coroutine.php在收到time命令时返回当前服务器时间date(Y-m-d H:i:s)。任务尝试把worker_num改为 2重启服务器。用 4 个 telnet 连接观察服务器日志中的进程 ID看看连接是如何被分配到不同 Worker 进程的。思考为什么在tcp_echo_coroutine.php的Receive回调中即使不加Swoole\Coroutine\System::sleep直接写sleep(5)它还会阻塞吗会的因为原生函数如果不做运行时协程化就是阻塞的。我们将在后面学习Runtime::enableCoroutine()来一键让原生函数协程化。今日作业与学习产出绘制流程图手绘或使用工具画出 Reactor 模型的事件循环流程图包括Connect、Receive、Close事件。提交代码将今天编写的两个tcp_echo_*.php文件提交到 Git 仓库。学习笔记用自己的话解释同步阻塞和协程非阻塞的差异并记录下你观察到的实验现象。