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

Hyperf 如何做到用两个端口 9501/9502 都能连接 Websocket 服务以及多 Worker 协作实现聊天室功能

为何 Hyperf 能够在两个端口上监听 WebSocket 连接?

源码角度来看,在配置了多个 Servers 时,实际上,只启动了一个 Server

注:我之前接触的代码都是启动一个服务绑定一个端口,之前也看过 swoole 扩展的文档,但是没留意服务和监听端口也是分离的,这启发了我一种思维,代码凡是能继续拆分的,就继续拆分,这样代码就会有更多的灵活,每个功能都能进行扩展,将服务和端口进行拆分之后,就可以在一个 Server 绑定多个 Port,每个 Port 又能有独立的事件。

/*** @param Port[] $servers* @return Port[]*/
protected function sortServers(array $servers): array
{$sortServers = [];foreach ($servers as $server) {switch ($server->getType()) {case ServerInterface::SERVER_HTTP:$this->enableHttpServer = true;if (! $this->enableWebsocketServer) {array_unshift($sortServers, $server);} else {$sortServers[] = $server;}break;case ServerInterface::SERVER_WEBSOCKET:$this->enableWebsocketServer = true;array_unshift($sortServers, $server);break;default:$sortServers[] = $server;break;}}return $sortServers;
}

从源码看,排在第一个的服务配置,会被创建服务,之后都是增加监听

protected function initServers(ServerConfig $config)
{$servers = $this->sortServers($config->getServers());foreach ($servers as $server) {$name = $server->getName();$type = $server->getType();$host = $server->getHost();$port = $server->getPort();$sockType = $server->getSockType();$callbacks = $server->getCallbacks();if (! $this->server instanceof SwooleServer) {$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);$this->registerSwooleEvents($this->server, $callbacks, $name);$this->server->set(array_replace($config->getSettings(), $server->getSettings()));ServerManager::add($name, [$type, current($this->server->ports)]);if (class_exists(BeforeMainServerStart::class)) {// Trigger BeforeMainServerStart event, this event only trigger once before main server start.$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));}} else {/** @var bool|\Swoole\Server\Port $slaveServer */$slaveServer = $this->server->addlistener($host, $port, $sockType);if (! $slaveServer) {throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");}$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));$this->registerSwooleEvents($slaveServer, $callbacks, $name);ServerManager::add($name, [$type, $slaveServer]);}// Trigger beforeStart event.if (isset($callbacks[Event::ON_BEFORE_START])) {[$class, $method] = $callbacks[Event::ON_BEFORE_START];if ($this->container->has($class)) {$this->container->get($class)->{$method}();}}if (class_exists(BeforeServerStart::class)) {// Trigger BeforeServerStart event.$this->eventDispatcher->dispatch(new BeforeServerStart($name));}}
}

从makeServer函数来看,如果服务中有SERVER_WEBSOCKET,则这个会被作为主服务启动,new SwooleWebSocketServer

protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType): SwooleServer
{switch ($type) {case ServerInterface::SERVER_HTTP:return new SwooleHttpServer($host, $port, $mode, $sockType);case ServerInterface::SERVER_WEBSOCKET:return new SwooleWebSocketServer($host, $port, $mode, $sockType);case ServerInterface::SERVER_BASE:return new SwooleServer($host, $port, $mode, $sockType);}throw new RuntimeException('Server type is invalid.');
}

$this->registerSwooleEvents($this->server, $callbacks, $name); 这句代码会将 Websocket 的各种事件都注册进去,于是主服务器拥有 websocket 的各种事件,而后 http 服务器挂载 9501 端口上,绑定了 onrequest 事件,但是如果有 websocket 连接9501 端口上时,默认该服务器是自动开启 websocket 自动升级的,又因为监听 9501 端口绑定的主服务器是 WebSocketServer,因此,WebSocketServer 默认的 onmessage,onopen事件就会被拿来用。

推测,如果开启 9503 WebSocket服务器,那么理论上用 WebSocket连接 9501 端口,应该就是连接的 9503 的回调事件。如果不想让 http 监听端口自动开启 websocket 协议,则将open_websocket_protocol=false

<?php
return [
// 这里省略了该文件的其它配置
'servers' => [['name' => 'http','type' => Server::SERVER_HTTP,'host' => '0.0.0.0','port' => 9501,'sock_type' => SWOOLE_SOCK_TCP,'callbacks' => [Event::ON_REQUEST => [Hyperf\HttpServer\Server::class,'onRequest'],],'settings' => ['open_websocket_protocol' => false,]],]];

关于很多SWOOLE中出现的常量来看,这些东西在执行脚本时,会被自动设置好,通过实际代码运行发现

define('SWOOLE_HTTP2_ERROR_COMPRESSION_ERROR', 9);
define('SWOOLE_HTTP2_ERROR_CONNECT_ERROR', 10);
define('SWOOLE_HTTP2_ERROR_ENHANCE_YOUR_CALM', 11);
define('SWOOLE_HTTP2_ERROR_INADEQUATE_SECURITY', 12);
define('SWOOLE_BASE', 1);
define('SWOOLE_PROCESS', 2);
define('SWOOLE_IPC_UNSOCK', 1);
define('SWOOLE_IPC_MSGQUEUE', 2);
define('SWOOLE_IPC_PREEMPTIVE', 3);

以下,随便设置的一个test.php中输出SWOOLE_BASE,都能输出1,我之前都以为这些常量是运行时设置的呢,看来这种理解是错误的。

<?php
echo SWOOLE_BASE."\n";
echo "hello\n";// 输出
1
hello

Hyperf-skeleton 给的默认配置就是进程模式,这个竟然没有发现,这样就比较明确了,使用的都是PROCESS模式,那么在websocket连接时,所有的连接都是由Manager来控制

SWOOLE_PRECESS 和 SWOOLE_BASE 两种模式

Server 的两种运行模式介绍

在 Swoole\Server 构造函数的第三个参数,可以填 2 个常量值 -- SWOOLE_BASE或 SWOOLE_PROCESS,下面将分别介绍这两个模式的区别以及优缺点

SWOOLE_PROCESS

SWOOLE_PROCESS 模式的 Server 所有客户端的 TCP 连接都是和主进程建立的,内部实现比较复杂,用了大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景。Swoole 提供了完善的进程管理、内存保护机制。 在业务逻辑非常复杂的情况下,也可以长期稳定运行。

Swoole 在 Reactor线程中提供了 Buffer 的功能,可以应对大量慢速连接和逐字节的恶意客户端。

进程模式的优点:

  • 连接与数据请求发送是分离的,不会因为某些连接数据量大某些连接数据量小导致 Worker 进程不均衡

  • Worker 进程发生致命错误时,连接并不会被切断

  • 可实现单连接并发,仅保持少量 TCP 连接,请求可以并发地在多个 Worker 进程中处理

进程模式的缺点:

  • 存在 2 次 IPC 的开销,master 进程与 worker 进程需要使用 unixSocket进行通信

  • SWOOLE_PROCESS 不支持 PHP ZTS,在这种情况下只能使用 SWOOLE_BASE 或者设置 single_thread为 true

SWOOLE_BASE

SWOOLE_BASE 这种模式就是传统的异步非阻塞 Server。与 Nginx 和 Node.js 等程序是完全一致的。

worker_num参数对于 BASE 模式仍然有效,会启动多个 Worker 进程。

当有 TCP 连接请求进来的时候,所有的 Worker 进程去争抢这一个连接,并最终会有一个 worker 进程成功直接和客户端建立 TCP 连接,之后这个连接的所有数据收发直接和这个 worker 通讯,不经过主进程的 Reactor 线程转发。

  • BASE 模式下没有 Master 进程的角色,只有 Manager进程的角色。

  • 每个 Worker 进程同时承担了 SWOOLE_PROCESS模式下 Reactor线程和 Worker 进程两部分职责。

  • BASE 模式下 Manager 进程是可选的,当设置了 worker_num=1,并且没有使用 Task 和 MaxRequest 特性时,底层将直接创建一个单独的 Worker 进程,不创建 Manager 进程

BASE 模式的优点:

  • BASE 模式没有 IPC 开销,性能更好

  • BASE 模式代码更简单,不容易出错

BASE 模式的缺点:

  • TCP 连接是在 Worker 进程中维持的,所以当某个 Worker 进程挂掉时,此 Worker 内的所有连接都将被关闭

  • 少量 TCP 长连接无法利用到所有 Worker 进程

  • TCP 连接与 Worker 是绑定的,长连接应用中某些连接的数据量大,这些连接所在的 Worker 进程负载会非常高。但某些连接数据量小,所以在 Worker 进程的负载会非常低,不同的 Worker 进程无法实现均衡。

  • 如果回调函数中有阻塞操作会导致 Server 退化为同步模式,此时容易导致 TCP 的 backlog队列塞满问题。

BASE 模式的适用场景:

如果客户端连接之间不需要交互,可以使用 BASE 模式。如 Memcache、HTTP 服务器等。

BASE 模式的限制:

在 BASE 模式下,Server 方法除了 send和 close以外,其他的方法都不支持跨进程执行。

Reactor 线程和 Worker 进程

Reactor 线程

  • Reactor 线程是在 Master 进程中创建的线程

  • 负责维护客户端 TCP 连接、处理网络 IO、处理协议、收发数据

  • 不执行任何 PHP 代码

  • 将 TCP 客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包

Worker 进程

  • 接受由 Reactor 线程投递的请求数据包,并执行 PHP 回调函数处理数据

  • 生成响应数据并发给 Reactor 线程,由 Reactor 线程发送给 TCP 客户端

  • 可以是异步非阻塞模式,也可以是同步阻塞模式

  • Worker 以多进程的方式运行

他们之间的关系可以理解为 Reactor 就是 nginx,Worker 就是 PHP-FPM。Reactor 线程异步并行地处理网络请求,然后再转发给 Worker 进程中去处理。Reactor 和 Worker 间通过 unixSocket进行通信。

在 PHP-FPM 的应用中,经常会将一个任务异步投递到 Redis 等队列中,并在后台启动一些 PHP 进程异步地处理这些任务。Swoole 提供的 TaskWorker 是一套更完整的方案,将任务的投递、队列、PHP 任务处理进程管理合为一体。通过底层提供的 API 可以非常简单地实现异步任务的处理。另外 TaskWorker 还可以在任务执行完成后,再返回一个结果反馈到 Worker。

Swoole 的 Reactor、Worker、TaskWorker 之间可以紧密的结合起来,提供更高级的使用方式。

一个更通俗的比喻,假设 Server 就是一个工厂,那 Reactor 就是销售,接受客户订单。而 Worker 就是工人,当销售接到订单后,Worker 去工作生产出客户要的东西。而 TaskWorker 可以理解为行政人员,可以帮助 Worker 干些杂事,让 Worker 专心工作。

结论

  1. SWOOLE_PROCESS 模式下,Websocket 的连接对象都是由 Server 来控制,创建一个 Reactor 后,将该连接交付给 Reactor 来管理,Reactor 会将请求分配给 Worker 处理,Worker 处理完后,再把消息发给Server,Server 将消息压入队列等具体的 Reactor 发送出去

  2. hyperf 中有一处进程间通信,目前还不清楚,这里为何要向其他进程求助,以及其他进程监听到消息后会不会多发

    1. 开发者说,如果是 SWOOLE_PROCESS模式下不会触发让其他 Worker 发送的机制,只有 SWOOLE_BASE 模式下,每个链接交给每个 Worker 单独处理时,才需要在多个 Worker 协作处理,因为每个 Worker 争抢到的连接都是隔离的,所以不会出现发送多个的情况

  3. 上面 2 给了一种分布式 websocket 构建方式,采用这种多进程的方式就能让多台服务器协作提供长连接服务,保证千级万级用户的接入量

 

 下面这段代码是来自onPipeMessage 监听器的,意味着其他 worker 收到后,判断是在自己进程上的连接就执行

 

 

 

相关文章:

Hyperf 如何做到用两个端口 9501/9502 都能连接 Websocket 服务以及多 Worker 协作实现聊天室功能

为何 Hyperf 能够在两个端口上监听 WebSocket 连接&#xff1f; 源码角度来看&#xff0c;在配置了多个 Servers 时&#xff0c;实际上&#xff0c;只启动了一个 Server 注&#xff1a;我之前接触的代码都是启动一个服务绑定一个端口&#xff0c;之前也看过 swoole 扩展的文档…...

网络映射会遇到哪些困难

网络映射通过将复杂的网络划分为更小、可管理的块&#xff0c;帮助 IT 管理员获得对其网络的更大控制和可见性&#xff0c;它有助于可视化不同的网络组件&#xff08;如服务器、交换机端口和路由器&#xff09;如何互连以执行其功能&#xff0c;通过表示网络设备的通信方式&…...

【jvm】类的主动使用和被动使用

目录 一、主动使用二、被动使用 一、主动使用 1.创建类的实例 2.访问某个类或接口的静态变量&#xff0c;或者对该静态变量赋值 3.调用类的静态方法 4.反射&#xff08;例如Class.forName(“com.learning.Test”)&#xff09; 5.初始化一个类的子类 6.java虚拟机启动时被标明为…...

如何选择合适的损失函数

目录 如何选择合适的损失函数 1、均方误差&#xff0c;二次损失&#xff0c;L2损失&#xff08;Mean Square Error, Quadratic Loss, L2 Loss&#xff09; 2、平均绝对误差&#xff0c;L1损失&#xff08;Mean Absolute Error, L1 Loss&#xff09; 3、MSE vs MAE &#xff…...

Java常见的排序算法

排序分为内部排序和外部排序&#xff08;外部存储&#xff09; 常见的七大排序&#xff0c;这些都是内部排序 。 1、插入排序&#xff1a;直接插入排序 1、插入排序&#xff1a;每次将一个待排序的记录&#xff0c;按其关键字的大小插入到前面已排序好的记录序列 中的适当位置…...

【C++】5、构建:CMake

文章目录 一、概述二、实战2.1 内部构建、外部构建2.2 CLion Cmake 一、概述 CMake 是跨平台构建工具&#xff0c;其通过 CMakeLists.txt 描述&#xff0c;并生成 native 编译配置文件&#xff1a; 在 Linux/Unix 平台&#xff0c;生成 makefile在苹果平台&#xff0c;可以生…...

【ARP欺骗】嗅探流量、限速、断网操作

【ARP欺骗】 什么是ARP什么是ARP欺骗ARP欺骗实现ARP断网限制网速嗅探流量 什么是ARP ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是一个TCP/IP协议&#xff0c;用于根据IP地址获取物理地址。在计算机网络中&#xff0c;当一个主机需要发…...

初步认识OSPF的大致内容(第三课)

1 路由的分类 直连路由(Directly Connected Route)是指网络拓扑结构中相邻两个网络设备直接相连的路由,也称为直接路由。如果两个设备属于同一IP网络地址,那么它们就是直连设备。直连路由表是指由计算机系统生成的一种用于路由选择的表格,其中记录着直连路由的信息。直连…...

CSDN编程题-每日一练(2023-08-27)

CSDN编程题-每日一练&#xff08;2023-08-27&#xff09; 一、题目名称&#xff1a;异或和二、题目名称&#xff1a;生命进化书三、题目名称&#xff1a;熊孩子拜访 一、题目名称&#xff1a;异或和 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述&#xff1a; …...

机器视觉之平面物体检测

平面物体检测是计算机视觉中的一个重要任务&#xff0c;它通常涉及检测和识别在图像或视频中出现的平面物体&#xff0c;如纸张、标志、屏幕、牌子等。下面是一个使用C和OpenCV进行平面物体检测的简单示例&#xff0c;使用了图像中的矩形轮廓检测方法&#xff1a; #include &l…...

C#开发WinForm之DataGridView开发

前言 DataGridView是开发Winform的一个列表展示&#xff0c;类似于表格。学会下面的基本特征用法&#xff0c;再辅以经验&#xff0c;基本功能开发没问题。 1.设置 DataGridView表格行首为序号索引, //设置 DataGridView表格行首为序号索引private void dataGridView1_RowPost…...

PDFPrinting.Net Crack

PDFPrinting.Net Crack 它能够轻松灵活地预测完美的打印结果以及用户文件的示例性显示。在.NET的PDF打印中&#xff0c;可以快速浏览最关键的元素。如果用户需要获得更详细的概述&#xff0c;那么他可以查看快速入门手册&#xff0c;甚至现有文档的详细概述参考。 在这种情况下…...

git操作:将一个仓库的分支提交到另外一个仓库分支

这个操作&#xff0c;一般是同步不同网站的同个仓库&#xff0c;比如说gitee 和github。某个网站更新了&#xff0c;你想同步他的分支过来。然后基于分支开发或者其它。 操作步骤 1.本地先clone 你自己的仓库。也就是要push 分支的仓库。比如A仓库&#xff0c;把B仓库分支&am…...

基于Java+SpringBoot+Vue前后端分离医院资源管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…...

Android——基本控件下(十七)

1. 文本切换&#xff1a;TextSwitcher 1.1 知识点 &#xff08;1&#xff09;理解TextSwitcher和ViewFactory的使用。 1.2 具体内容 范例&#xff1a;切换显示当前时间 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools&…...

HCIP-HCS华为私有云

1、概述 HCS&#xff08;HuaweiCoudStack&#xff09;华为私有云&#xff1a;6.3 之前叫FusionSphere OpenStack&#xff0c;6.3.1 版本开始叫FusionCloud&#xff0c;6.5.1 版本开始叫HuaweiCloud Stack (HCS)华为私有云软件。 开源openstack&#xff0c;发放云主机的流程&am…...

docker下载github项目失败

Docker 在构建过程中直接从 GitHub 下载项目时超时&#xff0c;可能是由于网络问题、GitHub 访问限制或其他原因导致的。以下是一些建议和解决方法&#xff1a; 预先下载项目: 在构建 Docker 镜像之前&#xff0c;首先在宿主机上手动克隆 GitHub 项目&#xff0c;然后使用 COPY…...

【CSS】网站 网格商品展示 模块制作 ( 清除浮动需求 | 没有设置高度的盒子且内部设置了浮动 | 使用双伪元素清除浮动 )

一、清除浮动需求 ( 没有设置高度的盒子且内部设置了浮动 ) 绘制的如下模块 : 在上面的盒子中 , 没有设置高度 , 只设置了一个 1215px 的宽度 ; 在列表中每个列表项都设置了 浮动 ; /* 网格商品展示 */ .box-bd {/* 处理列表间隙导致意外换行问题一排有 5 个 228x270 的盒子…...

文本分类任务

文章目录 引言1. 文本分类-使用场景2. 自定义类别任务3. 贝叶斯算法3.1 预备知识3.2 贝叶斯公式3.3 贝叶斯公式的应用3.4 贝叶斯公式在NLP中的应用3.5 贝叶斯公式-文本分类3.6 代码实现3.7 贝叶斯算法的优缺点 4. 支持向量机4.1 支持向量机-核函数4.2 支持向量机-解决多分类4.3…...

Pyecharts教程(一):Python中的pyecharts库绘制3D曲面图

Pyecharts教程(一):Python中的pyecharts库绘制3D曲面图 作者:安静到无声 个人主页 目录 Pyecharts教程(一):Python中的pyecharts库绘制3D曲面图实验结果推荐专栏在Python中,我们可以使用pyecharts库来绘制各种图表,如柱状图、折线图、饼图等。最近,我在学习如何使用pyec…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

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

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