这才是 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、 connectionrequest 参数回调到 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 的立命之本。

感谢大家阅读,个人观点仅供参考,欢迎在评论区发表不同观点。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/51734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++11 列表初始化与类型声明

目录 ​ 0.前言 1.C11介绍 2.统一的列表初始化 2.1{}初始化 2.2initializer_list 2.2.1initializer_list 的基本用法 2.2.2用于类的 initializer_list 构造函数 2.2.3与标准库容器的结合 2.2.4优势与注意事项 3.新声明 3.1auto 3.1.1基本用法 3.1.2优势 3.1.3注意事项 3.2declt…

npm国内淘宝镜像registry镜像过期

我们在使用npm的时候会遇到淘宝镜像安装过期的问题 首先了解 npm 淘宝镜像是一个 npm 注册表的镜像&#xff0c;用于加速 npm 包的下载。 一、如何设置&#xff1f; 如何设置淘宝镜像&#xff1f;淘宝镜像已经从 registry.npm.taobao.org 切换到了 registry.npmmirror.com n…

贪吃蛇 C++简易版(完整代码)

代码&#xff1a; #include <iostream> #include <conio.h> #include <vector> #include <windows.h> #include <ctime>using namespace std; //设置范围 const int WIDTH 20; const int HEIGHT 20;const char EMPTY ; const char SNAKE O…

代码随想录总结

文章目录 1. 回溯法1.1 组合1.2 组合总和31.3 电话号码的字母组合 2. 1. 回溯法 经典问题&#xff1a;排列、组合、切割、子集、棋盘 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的…

【SpringBoot教程:从入门到精通】掌握Springboot开发技巧和窍门(四)-Vue项目配置环境、导航栏

主要写前端页面&#xff0c;采用vue框架写页面的导航栏&#xff01;&#xff01;&#xff01; 文章目录 前言 Vue项目配置环境 安装依赖 创建菜单 总结 前言 主要写前端页面&#xff0c;采用vue框架写页面的导航栏&#xff01;&#xff01;&#xff01; Vue项目配置环境 安装…

【状语从句】

框架 概念&#xff0c;特点主将从现连接词时间条件地点结果方式让步原因目的比较省略倒装 解读 1【概念&#xff0c;特点】 一个完整的句子&#xff0c;去修饰另一个完整句子中的动词&#xff0c;称为状语从句&#xff1b;特点&#xff1a;从句完整&#xff0c;只用考虑连接词是…

Git远程仓库推送

这里我只连接了两个站点的远程仓库&#xff0c;一个是国内的Gitee&#xff0c;另一个是Github&#xff0c;这两个站点的连接方式主要有两种&#xff0c;第一种就是通过https来连接远程仓库&#xff0c;另一种是通过ssh公钥来连接&#xff0c;这两个站点练接的大致过程都是一样的…

Acrel-1000DP分布式光伏监控系统在江苏盛佳德新材料有限公司4.03MW分布式光伏10KV并网系统的应用

安科瑞戴婷 Acrel-Fanny 摘要&#xff1a;随着我国社会的不断进步,经济得到迅速的发展,城市化进程也逐渐加快。城市电力配电网络的供电压力逐渐增加,传统的发电技术已经不再适用于目前城市电力配电的发展现状。传统发电技术电力转化的效率比较低,对空气的污染也比较大,不利于城…

如何使用大语言模型绘制专业图表

过去的一年里&#xff0c;我相信大部分人都已经看到了大语言模型(后文简称LLM)所具备的自然语言理解和文本生成的能力&#xff0c;还有很多人将其应用于日常工作中&#xff0c;比如文案写作、资料查询、代码生成……今天我要向大家介绍LLM的一种新使用方式——绘图。这里说的绘…

SpringBoot整合PowerJob 实现远程任务

PowerJob介绍 PowerJob 是全新一代分布式任务调度和计算框架&#xff0c;提供了可视化界面&#xff0c;可通过单机、远程等形式调用任务并提供了运行监控和日志查看的功能模块&#xff0c;是当前比较流行的分布式定时任务框架之一&#xff1b; PowerJob 官网文档地址 环境搭建…

DC系列靶场---DC 3靶场的渗透测试(一)

信息收集 Nmap扫描 nmap -sS -sV -T4 -p- -O 172.30.1.142//-sS TCP的SYN扫描 //-sV 服务版本检测 //-T4 野蛮的扫描&#xff08;常用&#xff09; //-O 识别操作系统 使用Nmap扫描只看到一个80端口&#xff0c;Apache的2.4.18版本。 http探测 使用Wappalyzer插件可以到…

OSMDroidOfflineDemo源码调试记录

文章目录 源码下载环境配置尝试不同离线加载遇到的问题 尝试安卓端加载离线地图&#xff0c;下载了使用osmdroid的离线版项目源码&#xff0c;更改JDK环境、gradle环境&#xff0c;一顿操作下来&#xff0c;踉踉跄跄的把程序跑起来了&#xff0c;但是离线的地图一直加载不出来。…

机器人笛卡尔空间阻抗控制

机器人笛卡尔空间阻抗控制是一种重要的机器人控制策略,它关注于机器人末端执行器在笛卡尔空间(即任务空间)内的动态特性,以实现与环境的柔顺交互。以下是对机器人笛卡尔空间阻抗控制的详细解释: 一、基本概念 笛卡尔空间:指机器人末端执行器(如手爪、工具等)所处的三维…

QT基础教程(QEvent事件和事件过滤器)

文章目录 前言一、具体介绍二、具体案例1.鼠标事件2.键盘事件3.窗口事件 三、事件过滤器事件过滤器的工作原理 总结 前言 本篇文章将带大家来学习QT中的QEvent事件&#xff0c;QEvent 是 Qt 框架中的一个核心类&#xff0c;用于处理各种事件。在 Qt 的事件处理系统中&#xff…

C++学习日记 | LAB 10 运算符重载与友元函数

资料来源&#xff1a;南科大 于仕琪 C/C Program Design LINK&#xff1a;CPP/week10 at main ShiqiYu/CPP GitHub 一、本节内容 本节首先以一个例子具体演示和回顾操作符重载、友元函数以及重载<<操作符。习题部分则为各种运算符重载以及输入输出重载 1.1 Operator o…

nginx 版本升级

Nginx 的版本最开始使用的是 Nginx-1.18.0 &#xff0c; 由于服务升级&#xff0c;需要将 Nginx 的版本升级到 Nginx-1.19.7 &#xff0c;要求 Nginx 不能中断提供服务。 为了应对上述的需求&#xff0c;提供两种解决方案&#xff1a; 方案1&#xff1a; make upgrade 完成升…

包装类和泛型

&#x1f389;欢迎大家收看&#xff0c;请多多支持&#x1f339; &#x1f970;关注小哇&#xff0c;和我一起成长&#x1f680;个人主页&#x1f680; 包装类&#x1f319; Java中每个基本数据类型都对应了一个包装类&#xff0c; 除了int的包装类是Integer&#xff0c;char…

STM32项目分享:智能风扇系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.com/video/BV1xw4m1Y7sA…

详解 @RequestHeader 注解在 Spring Boot 中的使用

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119@qq.com] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? 专栏导…

springboot鲜花商城平台-计算机毕业设计源码56085

基于微信小程序的鲜花商城平台设计与实现 摘 要 鲜花商城小程序的研究旨在设计和开发一个方便、快捷的移动应用平台&#xff0c;为用户提供鲜花购买、资讯浏览和社交互动等功能。该研究包括以下几个方面的内容&#xff1a;首先&#xff0c;通过调研和分析鲜花市场和用户需求&a…