这才是 PHP 高性能框架 Workerman 的立命之本
大家好,我是码农先森。
在这个大家都崇尚高性能的时代,程序员的谈笑间句句都离不开高性能,仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low,其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解,其实高性能就是单位时间内能处理更多的客户端请求,如果要问具体能处理多少请求,这个就要结合软硬件条件来评估了,感兴趣的朋友可以在定性的条件下使用压力测试工具对自己的程序进行测试。
大家都知道 PHP-FPM 是 PHP 的进程管理器,每一次来自 Ngixn 转发过来的客户端请求,都会交由一个 PHP-FPM 子进程进行处理,在同一时刻一个子进程只能处理一个客户端请求,如果想要同一时刻能处理多个请求,那么就需要启动多个子进程,当遇到秒杀抢购这种瞬间大量请求的场景时,PHP-FPM 对请求处理的模式显然无法满足需求。在这种情况下,我们只能使用 Workerman 或 Swoole 这种 PHP 的高性能通信框架,来解决类似特殊场景下的并发问题,不过这次我分享的内容主要是 Workerman。
如标题所提到的 Workerman 立命之本,那什么是其立命之本呢?我认为是 IO 多路复用的 epoll 利器,epoll 是高性能程序的根基,解决 C10K 问题的尚方宝剑。接下来我会剖析 epoll 在 Workerman 源码中的使用,不过在这之前我们需要先学习下 PHP 中如何将 Socket 与 Event 结合使用的案例。这里的 Event 可以理解为是对 epoll 的高度封装,底层采用的就是 epoll 利器。
看了这段代码,有助于你理解 Workerman 源码,因为这段代码就是提炼了 Workerman 对事件循环的实现原理。stream_socket_server 函数把创建、绑定、监听一并实现了,让代码显得更加简洁,不像之前的 socket_create、socket_bind、socket_listen 搞了三个步骤略显繁琐。因为使用了事件循环,所以需要对 Socket 设置成非阻塞模式,只有当有读或写的通知时才会调用相应的回调函数。还有一点需要额外注意的,需要针对客户端 Socket 创建的 Event 需要定义成静态变量或全局变量,不然无法持久化连接到内存,会造成客户端无法建立连接传输数据,我看到网上很多人都踩到了这个坑上。最后启动事件循环 EventLoop 自此开启了 Socket 监听和事件循环双操作。
<?php// 创建 TCP 服务器套接字
$server = stream_socket_server("tcp://0.0.0.0:8080", $errno, $error);
echo "正在监听 8080 端口...". PHP_EOL; // 设置为非阻塞,在 $server 对象没有数据可以读取或写入时不会阻塞其执行
stream_set_blocking($server, 0);// 创建事件基础对象
$event_base = new EventBase();// 建立事件监听服务端 Socket 可读事件
$event = new Event($event_base, $server, Event::READ | Event::PERSIST, function ($server) use ($event_base) {// 获取新的连接,由于设置了非阻塞模式,那么这里即使没有新的连接,也不会一直阻塞在这$client = @stream_socket_accept($server, 0);if ($client) {echo "客户端(" . $client . ")连接建立". PHP_EOL; // 针对客户端过来的连接,也要设置成非阻塞模式stream_set_blocking($client, 0);// 客户端连接创建监听可读事件// 这里需要特别注意:客户端事件需要定义成静态变量或全局变量static $client_event;$client_event = new Event($event_base, $client, Event::READ | Event::PERSIST, function ($client) {// 从客户端连接中读取数据,每次只读取 1024 字节数据$buffer = fread($client, 1024);// 如果没有读取到数据或者客户端已经不是资源句柄,则关闭客户端连接if ($buffer == false || !is_resource($client)) {// 关闭客户端连接fclose($client);echo "客户端(" . $client . ")连接关闭" . PHP_EOL; return;}echo "收到客户端(" . $client . ")数据: $buffer" . PHP_EOL;// 回写数据给客户端$msg = "HTTP/1.0 200 OK\r\nContent-Length: 10\r\n\r\nServerOK\r\n";fwrite($client, $msg);}, $client);$client_event->add();}
}, $server);// 添加事件
$event->add();// 执行事件循环
$event_base->loop();
使用 CURL 工具访问 http://127.0.0.1:8080 便能正确返回结果 ServerOK 这表明事件循环可以进入正常运行状态。
[manongsen@root php_event]$ curl -i http://127.0.0.1:8080
HTTP/1.0 200 OK
Content-Length: 10ServerOK
看懂了上面那段代码之后,接下来的内容就会更顺利了。下面这段代码是引至 Workerman 的示例,通过 Worker 类构造了一个 HTTP 服务。onMessage 参数定义了一个回调函数,当有事件通知时,会回调到此处,之后就是用户自行实现后续的处理逻辑了。runAll 函数会整体启动整个服务,其中包括进程的创建、事件的循环等。
<?php// 引用 Worker 类
use Workerman\Worker;// 自动加载 Composer
require_once __DIR__ . '/vendor/autoload.php';// 定义 HTTP 服务并监听 8081 端口
$http_worker = new Worker('http://0.0.0.0:8081');// 定义回调函数
$http_worker->onMessage = function ($connection, $request) {//$request->get();//$request->post();//$request->header();//$request->cookie();//$request->session();//$request->uri();//$request->path();//$request->method();// Send data to client$connection->send("Hello World");
};// 启动服务
Worker::runAll();
在 Worker.php 文件的 2367 行,使用 stream_socket_server 函数创建了服务端 Socket 并且绑定、监听了 8081 端口。
// workerman/Worker.php:2367
$this->_mainSocket = \stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context);
在 Worker.php 文件的 2394 行,使用 stream_set_blocking 函数将 服务端 Socket 设置成非阻塞模式。
// workerman/Worker.php:2394
\stream_set_blocking($this->_mainSocket, false);
在 Worker.php 文件的 2417 行,将服务端的 _mainSocket 添加到事件循序中,并且设置回调函数为 acceptConnection 。
// workerman/Worker.php:2417
static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection'));
在 Worker.php 文件的 2561 行,使用 stream_socket_accept 接收到来自客户端的连接 $new_socket ,其中这个操作是在 acceptConnection 回到函数中所进行的。
// workerman/Worker.php:2561
$new_socket = \stream_socket_accept($socket, 0, $remote_address);
在 TcpConnection.php 文件的 285 行,使用 stream_set_blocking 函数将客户端的 _socket 设置成非阻塞模式,这里的 _socket 和上面的 new_socket 是同一个。
// workerman/Connection/TcpConnection.php:285
\stream_set_blocking($this->_socket, 0);
在 TcpConnection.php 文件的 290 行,将客户端的 _socket 添加到事件循环中,并且设置其的回调函数为 baseRead 。
// workerman/Connection/TcpConnection.php:290
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
在 Worker.php 文件的 1638 行,启动事件循环。
// workerman/Worker.php:1638
static::$globalEvent->loop();
启动事件循环后,当有客户端连接时便可以读取数据了。因此在 TcpConnection.php 文件的 583 行,使用 fread 函数读取客户端 $socket 的数据。
// workerman/Connection/TcpConnection.php:583
$buffer = @\fread($socket, self::READ_BUFFER_SIZE);
在 TcpConnection.php 文件的 647 行,使用 parser::decode 函数将上面读取到的 buffer 数据解析成 $request 对象,还有 $this 表示的是 $connection 对象,这个 $this->onMessage 是最开始用户自定义的回调函数。最终通过 call_user_func 函数,将 c o n n e c t i o n 、 connection、 connection、request 参数回调到 onMessage 方法。
// workerman/Connection/TcpConnection.php:647
\call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));
最后我们使用 CURL 工具调用一下 http://127.0.0.1:8081 通过返回的数据,可以看出正确的回调到了 onMessage 函数。
[manongsen@root workerman]$ curl -i http://127.0.0.1:8081
HTTP/1.1 200 OK
Server: workerman
Connection: keep-alive
Content-Type: text/html;charset=utf-8
Content-Length: 13Hello World
看到这里相信你已经对 Workerman 源码中的事件循环有些了解了,如果有时间最好能够实践下最开始的那段案例代码,然后再结合着看 Workerman 的源代码会颇有收获。Workerman 的高性能是站在了巨人 epoll 的肩膀上来实现,没有了 epoll 则啥也不是。这里再重申一下 PHP 中的 Event 是对 epoll 的封装,epoll 是 Linux 的底层技术。我们在日常的编程中是不会直接接触到 epoll 的,最后回归一下主题 epoll 技术才是 Workerman 的立命之本。
感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。
欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

相关文章:
这才是 PHP 高性能框架 Workerman 的立命之本
大家好,我是码农先森。 在这个大家都崇尚高性能的时代,程序员的谈笑间句句都离不开高性能,仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low,其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解…...
Python——记录pip问题(解决下载慢、升级失败问题)
在python开发中,经常需要使用到各种各样的库。 pip又是我们常用的安装工具。但是国外的源下载速度实在太慢,经常导致超时。 有很多朋友刚刚学Python的时候,会来问为什么pip下载东西这么慢啊? 而且pycharm里面下载库也是非常的慢…...
Windows Server 2025 Preview 部署 Ⅰ—— ISO下载和硬件要求
目录 1. 预览版下载2. Windows 服务器的硬件要求2.1安装 Windows Server 2025 虚拟机注意事项2.2 CPU2.3 RAM / Memory2.4 存储 Storage2.5 网络 Network2.6 其他需求 1. 预览版下载 下载网站:https://www.microsoft.com/en-in/evalcenter/download-windows-server…...
AI2-CUDA、CuDNN、TensorRT的详细安装教程
一、查看本机的显卡 首先你要看你的电脑是否有NVIDIA的独立显卡,你可以在设备管理器-显示适配器中查看 点击“开始”--找到“NVIDA Control Panel” 点击帮助--系统信息--组件,查看NVCUDA.DLL对应的产品名称,就可以看住CUDA的版本号 这里的版…...
TCP连接中重复使用了两个相同的端口怎么办
1.检查并避免重复配置: 首先,应该检查系统的配置和应用程序的设置,确保没有错误地将多个服务或进程配置为使用相同的端口号。 使用网络监控工具(如netstat、ss等)来查看当前哪些端口正在被使用。 2.使用端口复用选项…...
如何自定义异常
目录 为什么自定义异常自定义异常的步骤参考资料 为什么自定义异常 自定义异常是指开发者根据应用程序的需求和逻辑,自行定义的异常类。与 Java 提供的标准异常类(如 NullPointerException、IOException 等)不同,自定义异常允许程…...
C++中的依赖注入
目录 1.概述 2.构造函数注入 3.setter方法注入 4.接口注入 5.依赖注入框架 6.依赖注入容器 7.依赖注入框架的工作原理 8.依赖注入的优势 9.总结 1.概述 依赖注入是一种设计模式,它允许我们在不直接创建对象的情况下为对象提供其依赖项;它通过将…...
CSS平面转换-平移
平面转换-平移 属性 transform: translate(X轴移动距离, Y轴移动距离); 取值 像素单位取值百分比(参照盒子自身尺寸计算结果)正负均可 技巧translate()只写一个值表示只沿着X轴移动单独设置X或Y轴距离:translateX()或translateY() 代码 …...
Linux-3:Shell编程——基础语法(0-50%)
目录 前言 一、变量 1.定义变量 2.使用变量 3.修改变量 4.将命令的结果赋值给变量 5.只读变量 6.删除变量 二、传递参数 三、字符串 1.字符串举例 2.统计字符串长度 3.字符串拼接 4.截取字符串 总结 前言 Shell是一种程序设计语言。作为命令语言,它…...
C++ --> string类模拟实现(附源码)
欢迎来到我的Blog,点击关注哦💕 前言: C中STL扮演着极其重要的角色,学习C重中之重的就是学习STL,虽然string不作为containers的其中一员,但是也是值得学习的le类。下面就进行string的模拟实现 string的模拟…...
基于PHP+MySQL组合开发的微信活动投票小程序源码系统 带完整的安装代码包以及搭建部署教程
系统概述 在当今数字化时代,微信作为社交媒体的巨头,为企业和个人提供了丰富的互动营销平台。其中,投票活动作为一种有效的用户参与和互动方式,被广泛应用于各种场景。为了满足这一需求,我们推出了一款基于PHPMySQL组…...
利用Arcgis设置分式标注(分子分母标注)
因工作需要,需要设置分式标注,下面详细介绍下如何利用arcgis 设置分式标注,以下操作以供参考,如有疑义可提出。 一、准备工作 软件:arcmap 示例数据:行政区shp矢量图 二、操作步骤 1.添加数据 将行政区sh…...
大麦网抢票攻略:使用Python Selenium实现
随着互联网技术的发展,在线购票已成为人们获取演出、比赛等活动门票的主要方式。然而,面对热门活动,门票往往在开售瞬间被抢购一空。为了解决这一问题,本文将介绍如何利用Python和Selenium技术实现大麦网的自动抢票。 1. 环境准备…...
Navicat 在整个数据库中查找字符
Navicat 在整个数据库中查找字符 1.首先打开Navicat,连接目标数据库。2.选择工具选项卡,选择在数据库或模式中查找。3.查找前填入关键字信息,点击查找4.双击查找到数据,进行查看 说明:当我们知道数据库有数据的关键字,…...
Python基础—处理时间问题
一文帮助您解决99%时间处理问题...
如何选择合适的自动化测试工具!
选择合适的自动化测试工具是一个涉及多方面因素的决策过程。以下是一些关键步骤和考虑因素,帮助您做出明智的选择: 一、明确测试需求和目标 测试范围:确定需要自动化的测试类型(如单元测试、集成测试、UI测试等)和测试…...
数字图像边缘曲率计算及特殊点检测
一、曲率和数字图像边缘曲率检测常用方法简介 边缘曲率作为图像边缘特征的重要参数,不仅反映了边缘的几何形状信息,还对于图像识别、图像分割、目标跟踪等任务具有显著影响。 曲线的曲率(curvature)就是针对曲线上某个点的切线方向…...
python map
在 Python 中,如果你指的是字典(dictionary),可以使用字典的 .items() 方法来遍历键值对。Python 中没有严格意义上的 Map 类型,而是使用字典来实现类似于键值对映射的功能。 遍历字典的键值对 可以使用 for 循环和 …...
每日一练 - NFV部署应用环境
01 真题题目 NFV 常常部署在下列哪些应用环境中?(多选) A.数据中心 B.网络节点 C.用户接入侧 D.客户机/服务器 02 真题答案 ABC 03 答案解析 NFV(Network Functions Virtualization,网络功能虚拟化)是一种技术,它允许在标准的…...
031-GeoGebra中级篇-GeoGebra的布尔值
在 GeoGebra 中,布尔值和条件判断是实现动态数学模型和交互式几何图形的重要工具。布尔值,即逻辑值,只有两个可能的取值:真(True)或假(False)。通过使用布尔值,我们可以创…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
