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

这才是 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 的立命之本。

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


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

相关文章:

这才是 PHP 高性能框架 Workerman 的立命之本

大家好&#xff0c;我是码农先森。 在这个大家都崇尚高性能的时代&#xff0c;程序员的谈笑间句句都离不开高性能&#xff0c;仿佛嘴角边不挂着「高性能」三个字都会显得自己很 Low&#xff0c;其中众所皆知的 Nginx 就是高性能的代表。有些朋友可能连什么是高性能都不一定理解…...

Python——记录pip问题(解决下载慢、升级失败问题)

在python开发中&#xff0c;经常需要使用到各种各样的库。 pip又是我们常用的安装工具。但是国外的源下载速度实在太慢&#xff0c;经常导致超时。 有很多朋友刚刚学Python的时候&#xff0c;会来问为什么pip下载东西这么慢啊&#xff1f; 而且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. 预览版下载 下载网站&#xff1a;https://www.microsoft.com/en-in/evalcenter/download-windows-server…...

AI2-CUDA、CuDNN、TensorRT的详细安装教程

一、查看本机的显卡 首先你要看你的电脑是否有NVIDIA的独立显卡&#xff0c;你可以在设备管理器-显示适配器中查看 点击“开始”--找到“NVIDA Control Panel” 点击帮助--系统信息--组件&#xff0c;查看NVCUDA.DLL对应的产品名称&#xff0c;就可以看住CUDA的版本号 这里的版…...

TCP连接中重复使用了两个相同的端口怎么办

1.检查并避免重复配置&#xff1a; 首先&#xff0c;应该检查系统的配置和应用程序的设置&#xff0c;确保没有错误地将多个服务或进程配置为使用相同的端口号。 使用网络监控工具&#xff08;如netstat、ss等&#xff09;来查看当前哪些端口正在被使用。 2.使用端口复用选项…...

如何自定义异常

目录 为什么自定义异常自定义异常的步骤参考资料 为什么自定义异常 自定义异常是指开发者根据应用程序的需求和逻辑&#xff0c;自行定义的异常类。与 Java 提供的标准异常类&#xff08;如 NullPointerException、IOException 等&#xff09;不同&#xff0c;自定义异常允许程…...

C++中的依赖注入

目录 1.概述 2.构造函数注入 3.setter方法注入 4.接口注入 5.依赖注入框架 6.依赖注入容器 7.依赖注入框架的工作原理 8.依赖注入的优势 9.总结 1.概述 依赖注入是一种设计模式&#xff0c;它允许我们在不直接创建对象的情况下为对象提供其依赖项&#xff1b;它通过将…...

CSS平面转换-平移

平面转换-平移 属性 transform: translate(X轴移动距离, Y轴移动距离); 取值 像素单位取值百分比&#xff08;参照盒子自身尺寸计算结果&#xff09;正负均可 技巧translate()只写一个值表示只沿着X轴移动单独设置X或Y轴距离&#xff1a;translateX()或translateY() 代码 …...

Linux-3:Shell编程——基础语法(0-50%)

目录 前言 一、变量 1.定义变量 2.使用变量 3.修改变量 4.将命令的结果赋值给变量 5.只读变量 6.删除变量 二、传递参数 三、字符串 1.字符串举例 2.统计字符串长度 3.字符串拼接 4.截取字符串 总结 前言 Shell是一种程序设计语言。作为命令语言&#xff0c;它…...

C++ --> string类模拟实现(附源码)

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言&#xff1a; C中STL扮演着极其重要的角色&#xff0c;学习C重中之重的就是学习STL&#xff0c;虽然string不作为containers的其中一员&#xff0c;但是也是值得学习的le类。下面就进行string的模拟实现 string的模拟…...

基于PHP+MySQL组合开发的微信活动投票小程序源码系统 带完整的安装代码包以及搭建部署教程

系统概述 在当今数字化时代&#xff0c;微信作为社交媒体的巨头&#xff0c;为企业和个人提供了丰富的互动营销平台。其中&#xff0c;投票活动作为一种有效的用户参与和互动方式&#xff0c;被广泛应用于各种场景。为了满足这一需求&#xff0c;我们推出了一款基于PHPMySQL组…...

利用Arcgis设置分式标注(分子分母标注)

因工作需要&#xff0c;需要设置分式标注&#xff0c;下面详细介绍下如何利用arcgis 设置分式标注&#xff0c;以下操作以供参考&#xff0c;如有疑义可提出。 一、准备工作 软件&#xff1a;arcmap 示例数据&#xff1a;行政区shp矢量图 二、操作步骤 1.添加数据 将行政区sh…...

大麦网抢票攻略:使用Python Selenium实现

随着互联网技术的发展&#xff0c;在线购票已成为人们获取演出、比赛等活动门票的主要方式。然而&#xff0c;面对热门活动&#xff0c;门票往往在开售瞬间被抢购一空。为了解决这一问题&#xff0c;本文将介绍如何利用Python和Selenium技术实现大麦网的自动抢票。 1. 环境准备…...

Navicat 在整个数据库中查找字符

Navicat 在整个数据库中查找字符 1.首先打开Navicat,连接目标数据库。2.选择工具选项卡&#xff0c;选择在数据库或模式中查找。3.查找前填入关键字信息&#xff0c;点击查找4.双击查找到数据&#xff0c;进行查看 说明&#xff1a;当我们知道数据库有数据的关键字&#xff0c;…...

Python基础—处理时间问题

一文帮助您解决99%时间处理问题...

如何选择合适的自动化测试工具!

选择合适的自动化测试工具是一个涉及多方面因素的决策过程。以下是一些关键步骤和考虑因素&#xff0c;帮助您做出明智的选择&#xff1a; 一、明确测试需求和目标 测试范围&#xff1a;确定需要自动化的测试类型&#xff08;如单元测试、集成测试、UI测试等&#xff09;和测试…...

数字图像边缘曲率计算及特殊点检测

一、曲率和数字图像边缘曲率检测常用方法简介 边缘曲率作为图像边缘特征的重要参数&#xff0c;不仅反映了边缘的几何形状信息&#xff0c;还对于图像识别、图像分割、目标跟踪等任务具有显著影响。 曲线的曲率&#xff08;curvature&#xff09;就是针对曲线上某个点的切线方向…...

python map

在 Python 中&#xff0c;如果你指的是字典&#xff08;dictionary&#xff09;&#xff0c;可以使用字典的 .items() 方法来遍历键值对。Python 中没有严格意义上的 Map 类型&#xff0c;而是使用字典来实现类似于键值对映射的功能。 遍历字典的键值对 可以使用 for 循环和 …...

每日一练 - NFV部署应用环境

01 真题题目 NFV 常常部署在下列哪些应用环境中?(多选) A.数据中心 B.网络节点 C.用户接入侧 D.客户机/服务器 02 真题答案 ABC 03 答案解析 NFV&#xff08;Network Functions Virtualization&#xff0c;网络功能虚拟化&#xff09;是一种技术&#xff0c;它允许在标准的…...

031-GeoGebra中级篇-GeoGebra的布尔值

在 GeoGebra 中&#xff0c;布尔值和条件判断是实现动态数学模型和交互式几何图形的重要工具。布尔值&#xff0c;即逻辑值&#xff0c;只有两个可能的取值&#xff1a;真&#xff08;True&#xff09;或假&#xff08;False&#xff09;。通过使用布尔值&#xff0c;我们可以创…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...