当前位置: 首页 > 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;我们可以创…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...