05 | Swoole 源码分析之 WebSocket 模块
首发原文链接:Swoole 源码分析之 WebSocket 模块
大家好,我是码农先森。
引言
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输。
与传统的 HTTP 请求-响应模型不同,WebSocket 可以保持双向通信通道,从而使得服务器能够主动向客户端推送数据。
Swoole 中的 WebSocket 服务
下面这段代码是从Swoole 官方网站上的引用,从代码中可以看出创建了一个 WebScoket
对象且设置对应的 IP 地址及监听端口,同时还设置了四个回调方法处理对应的事件。
最后,调用 $server->start()
真正的启动 WebScoket
服务。
$server = new Swoole\Websocket\Server('127.0.0.1', 9502);$server->on('start', function ($server) {echo "Websocket Server is started at ws://127.0.0.1:9502\n";
});$server->on('open', function($server, $req) {echo "connection open: {$req->fd}\n";
});$server->on('message', function($server, $frame) {echo "received message: {$frame->data}\n";$server->push($frame->fd, json_encode(['hello', 'world']));
});$server->on('close', function($server, $fd) {echo "connection close: {$fd}\n";
});$server->start();
那么接下来,我们就从源码角度来分析 Swoole 对 WebSocket 的实现。
源码拆解
这个函数的主要作用是启动 Server 服务。
static void php_swoole_server_onStart(Server *serv) {// 锁定 Server 对象操作serv->lock();// 从 Server 对象中获取到 onStart 回调函数zval *zserv = (zval *) serv->private_data_2;ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv));auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onStart];...// 通过 zend::function::call 调用 PHP 层注册的 onStart 处理函数,并传递参数if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));}// 解锁 Server 对象操作serv->unlock();
}
这个函数主要作用是 WebSocket 服务针对客户端建立连接时事件的处理。
void swoole_websocket_onOpen(Server *serv, HttpContext *ctx) {// 通过 session_id 获取与特定客户端连接相关的 Connection 对象Connection *conn = serv->get_connection_by_session_id(ctx->fd);if (!conn) {swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd);return;}// Server 对象中获取在 PHP 层设置的回调函数 onOpen。zend_fcall_info_cache *fci_cache = php_swoole_server_get_fci_cache(serv, conn->server_fd, SW_SERVER_CB_onOpen);if (fci_cache) {zval args[2];args[0] = *((zval *) serv->private_data_2);args[1] = *ctx->request.zobject;// 通过 zend::function::call 调用 PHP 层注册的 onOpen 处理函数,并传递参数if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onOpen handler error", ZSTR_VAL(swoole_websocket_server_ce->name));serv->close(ctx->fd, false);}}
}
这个函数主要作用是 WebSocket 服务器针对客户端发送消息事件的处理。
int swoole_websocket_onMessage(Server *serv, RecvData *req) {SessionId fd = req->info.fd;uchar flags = 0;zend_long opcode = 0;// 从接收到的数据中获取客户端的 session_id,并根据 session_id 获取对应的端口信息auto port = serv->get_port_by_session_id(fd);if (!port) {return SW_ERR;}zval zdata;char frame_header[2];// 从接收到的数据中解析出 WebSocket 消息的帧头信息和消息内容memcpy(frame_header, &req->info.ext_flags, sizeof(frame_header));php_swoole_get_recv_data(serv, &zdata, req);// 解析出 WebSocket 消息的标志位和操作码flags = frame_header[0];opcode = frame_header[1];// 根据操作码和服务的设置,判断是否需要特殊处理 Close、Ping 或 Pong 类型的消息if ((opcode == WebSocket::OPCODE_CLOSE && !port->open_websocket_close_frame) ||(opcode == WebSocket::OPCODE_PING && !port->open_websocket_ping_frame) ||(opcode == WebSocket::OPCODE_PONG && !port->open_websocket_pong_frame)) {if (opcode == WebSocket::OPCODE_PING) {...}zval_ptr_dtor(&zdata);return SW_OK;}...// Server 对象中获取在 PHP 层设置的回调函数 onMessagezend_fcall_info_cache *fci_cache =php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onMessage);zval args[2];args[0] = *(zval *) serv->private_data_2;// 构造一个 WebSocket 消息帧的数据结构,并将结果存储在 args[1]php_swoole_websocket_construct_frame(&args[1], opcode, &zdata, flags);zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(&args[1]), ZEND_STRL("fd"), fd);// 通过 zend::function::call 调用 PHP 层注册的 onMessage 处理函数,并传递相应参数if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) {php_swoole_error(E_WARNING, "%s->onMessage handler error", ZSTR_VAL(swoole_websocket_server_ce->name));serv->close(fd, false);}// 释放 zdata 和 args[1] 占用的内存zval_ptr_dtor(&zdata);zval_ptr_dtor(&args[1]);return SW_OK;
}
这个函数的主要作用是关闭 Server 服务。
void php_swoole_server_onClose(Server *serv, DataHead *info) {...// Server 对象中获取在 PHP 层设置的回调函数 onCloseauto *fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onClose);Connection *conn = serv->get_connection_by_session_id(session_id);if (!conn) {return;}// 检查当前的 WebSocket 连接状态是否为非活动状态if (conn->websocket_status != swoole::websocket::STATUS_ACTIVE) {// 获取与当前连接相关的监听端口信息ListenPort *port = serv->get_port_by_server_fd(info->server_fd);// 如果该端口开启了 WebSocket 协议,且设置了 onDisconnect 回调函数if (port && port->open_websocket_protocol &&php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onDisconnect)) {// 获取 onDisconnect 回调函数fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onDisconnect);}}if (fci_cache) {...// 通过 zend::function::call 调用 PHP 层注册的 onDisconnect 处理函数,并传递相应参数if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) {php_swoole_error(E_WARNING, "%s->onClose handler error", SW_Z_OBJCE_NAME_VAL_P(zserv));}...}...}
这个函数的作用是断开 WebSocket 客户端的连接,并发送关闭帧。
static PHP_METHOD(swoole_websocket_server, disconnect) {// 从 ZEND_THIS 中获取 Server 对象Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS);...// 清空全局的 WebSocket 缓冲区swoole_websocket_buffer->clear();// 将关闭帧数据打包到 WebSocket 缓冲区中if (WebSocket::pack_close_frame(swoole_websocket_buffer, code, data, length, 0) < 0) {RETURN_FALSE;}// 调用 swoole_websocket_server_close 函数来关闭客户端连接,并返回结果RETURN_BOOL(swoole_websocket_server_close(serv, fd, swoole_websocket_buffer, 1));
}
这个函数的作用是在 WebSocket 服务中关闭客户端连接的操作。
static sw_inline bool swoole_websocket_server_close(Server *serv, SessionId fd, String *buffer, bool real_close) {// 尝试将数据推送给客户端,用于判断是否已经关闭连接bool ret = swoole_websocket_server_push(serv, fd, buffer);if (!ret || !real_close) {return ret;}// 获取到客户端连接相关的 Connection 对象Connection *conn = serv->get_connection_by_session_id(fd);if (conn) {// 将该连接的 websocket_status 改变为 WebSocket::STATUS_CLOSINGconn->websocket_status = WebSocket::STATUS_CLOSING;// 立即关闭连接return serv->close(fd, false);} else {return false;}
}
总结
- 在 Swoole 中 WebSocket 服务是继承于 Http 服务。
- 在实际的使用过程中是通过 Http 服务来握手升级成 WebSocket 服务。
- WebSocket 协议的出现解决了通过传统轮询方式来通信的效率问题。
- 同时也为 PHP 在双向通信解决方式上提供了新的解决方案。
相关文章:
05 | Swoole 源码分析之 WebSocket 模块
首发原文链接:Swoole 源码分析之 WebSocket 模块 大家好,我是码农先森。 引言 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输。 与传统的 HTTP 请求-响应模型不同,WebSocket 可以保持…...
Vue--------父子/兄弟组件传值
父子组件 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。 defineProps接收 let props defineProps({data: Array, }); defineModel接收 let bb defineModel("sit…...
Qt实现Kermit协议(一)
1 概述 Kermit文件运输协议提供了一条从大型计算机下载文件到微机的途径。它已被用于进行公用数据传输。 其特性如下: Kermit文件运输协议是一个半双工的通信协议。它支持7位ASCII字符。数据以可多达96字节长度的可变长度的分组形式传输。对每个被传送分组需要一个确认。Kerm…...
linux在使用重定向写入文件时(使用标准C库函数时)使处理信号异常(延时)--问题分析
linux在使用重定向写入文件时(使用标准C库函数时)使处理信号异常(延时)–问题分析 在使用alarm函数进行序号处理测试的时候发现如果把输出重定向到文件里面会导致信号的处理出现严重的延迟(ubuntu18) #include <stdio.h> #include <stdlib.h> #include <unist…...
淘宝扭蛋机小程序:趣味购物新体验,惊喜连连等你来
在数字化时代,淘宝始终站在创新的前沿,不断探索和引领电商行业的发展趋势。今天,我们欣然宣布,经过精心研发和打磨,淘宝扭蛋机小程序正式上线,为用户带来一场充满趣味与惊喜的购物新体验。 淘宝扭蛋机小程…...
linux:生产者消费者模型
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、生产者消费者模型二、基于阻塞队列的生产者消费者模型代码实现 总结 前言 本文是对于生产者消费者模型的知识总结 一、生产者消费者模型 生产者消费者模型就是…...
C++教学——从入门到精通 5.单精度实数float
众所周知,三角形的面积公式是(底*高)/2 那就来做个三角形面积计算器吧 到吗如下 #include"bits/stdc.h" using namespace std; int main(){int a,b;cin>>a>>b;cout<<(a*b)/2; } 这不对呀,明明是7.5而他却是7,…...
面向对象设计之单一职责原则
设计模式专栏:http://t.csdnimg.cn/6sBRl 目录 1.单一职责原则的定义和解读 2.如何判断类的职责是否单一 3.类的职责是否越细化越好 4.总结 1.单一职责原则的定义和解读 单一职责原则(Single Responsibility Principle,SRP)的描述:一个类…...
蓝桥杯真题:单词分析
import java.util.Scanner; //1:无需package //2: 类名必须Main, 不可修改 public class Main{public static void main(String[]args) {Scanner sannernew Scanner(System.in);String strsanner.nextLine();int []anew int [26];for(int i0;i<str.length();i) {a[str.charA…...
Python字符串字母大小写变换,高级Python开发技术
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书! ‘’’ demo ‘tHis iS a GOod boOK.’ print(demo.casefold()) print(demo.lower()) print(demo.upper()) print(demo.capitalize()) print(demo.title()) print(dem…...
CentOS常用功能命令集合
1、删除指定目录下所有的空目录 find /xxx -type d -empty -exec rmdir {} 2、删除指定目录下近7天之前的日志文件 find /xxx -name "*.log" -type f -mtime 7 -exec rm -f {} \; 3、查询指定目录下所有的指定格式文件(比如PDF文件) find…...
黑马点评项目笔记 II
基于Stream的消息队列 stream是一种数据类型,可以实现一个功能非常完善的消息队列 key:队列名称 nomkstream:如果队列不存在是否自动创建,默认创建 maxlen/minid:设置消息队列的最大消息数量 *|ID 唯一id:…...
关于一篇知乎答案的重现
〇、前言 早上在逛知乎的时候,瞥见了一篇答案:如何通俗解释Docker是什么?感觉很不错,然后就耐着性子看了下,并重现了作者的整个过程。但是并不顺利,记载一下这些坑。嫌麻烦的话可以直接clone 研究…...
实时数据库测试-汇编小程序
实时数据库测试-汇编小程序。 hd.asm .686 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\gdi32.inc …...
HTML5 、CSS3 、ES6 新特性
HTML5 新特性 1. 新的语义化元素:article 、footer 、header 、nav 、section 2. 表单增强,新的表单控件:calendar 、date 、time 、email 、url 、search 3. 新的 API:音频(用于媒介回放的 video 和 audio 元素)、图形&#x…...
基于springboot+vue实现的驾校信息管理系统
作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】:Java 【框架】:spring…...
X进制减法(贪心算法C++实现)
题目 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制,因为其每一数位的进制并不固定! 例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X 进制数 321 转换为十…...
[Windows]服务注册工具(nssm)
文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景:例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接:https://pan.baidu.com/s/111fkBWIS7CTlWIj80Kc8Sg?pwdanan 提取码…...
Xilinx缓存使用说明和测试
Xilinx缓存使用说明和测试 1 BRAM说明2 FIFO说明3 实例测试3.1 代码3.2 仿真本文主要介绍Xilinx FPGA芯片中BRAM和FIFO的使用方法和测试结果,主要针对流接口进行仿真。 1 BRAM说明 BRAM是Xilinx芯片中重要的存储资源,其可配置为单端口RAM/ROM或者双端口RAM/ROM,本文以最复杂…...
LeetCode:2952. 需要添加的硬币的最小数量(贪心 Java)
目录 2952. 需要添加的硬币的最小数量 题目描述: 实现代码与解析: 贪心 原理思路: 2952. 需要添加的硬币的最小数量 题目描述: 给你一个下标从 0 开始的整数数组 coins,表示可用的硬币的面值,以及一个…...
企业员工在线培训系统功能介绍
随着信息技术的飞速发展,企业员工培训方式正逐步从传统的面授转向灵活高效的在线培训。一个综合性的企业员工在线培训系统能够为员工提供多样化的学习资源、便捷的学习途径和有效的学习监督,以下是该系统的主要功能详细介绍: 1. 课程功能 线…...
服了,一线城市的后端都卷成这样了吗!?
声明:本文首发在同名公众号:王中阳Go,未经授权禁止转载。 先听TA的故事 投稿的主人公是一名工作5年的后端开发工程师,最近2年用Golang,之前其他语言。去年春节前被裁员了,各种心酸史,好愁人啊。…...
Qt扫盲-QAssisant 集成其他qch帮助文档
QAssisant 集成其他qch帮助文档 一、概述二、Cmake qch例子1. 下载 Cmake.qch2. 添加qch1. 直接放置于Qt 帮助的目录下2. 在 QAssisant中添加 一、概述 QAssisant是一个很好的帮助文档,他提供了供我们在外部添加新的 qch帮助文档的功能接口,一般有两中添…...
[lesson01]学习C++的意义
学习C的意义 C语言特点 C语言是在实践的过程中逐步完善起来的 没有深思熟路的设计过程残留量过多低级语言的特征 C语言的目标是高效 最终程序执行效率的高效 软件方法论的发展 面相过程程序设计:数据结构 算法 主要解决科学计算问题,用户需求简单而…...
LabVIEW双通道太阳射电频谱观测系统
LabVIEW双通道太阳射电频谱观测系统 开发了一个基于LabVIEW平台开发的双通道高速太阳射电频谱观测系统。该系统实时监测太阳射电爆发,具有随机性、持续时间短、变化快等特点。通过高速信号采集卡实现1.5 GS/s的信号采集,时间分辨率可达4ms,频…...
Trapcode Particular---打造惊艳粒子效果
Trapcode Particular是Adobe After Effects中的一款强大3D粒子系统插件,其能够创造出丰富多样的自然特效,如烟雾、火焰和闪光,以及有机的和高科技风格的图形效果。Trapcode Particular功能丰富且特色鲜明,是一款为Adobe After Eff…...
从0到1利用express搭建后端服务
目录 1 架构的选择2 环境搭建3 安装express4 创建启动文件5 express的核心功能6 加入日志记录功能7 日志记录的好处本节代码总结 不知不觉学习低代码已经进入第四个年头了,既然低代码很好,为什么突然又自己架构起后端了呢?我有一句话叫低代码…...
pytest和unittest 如何选择?
目录 如何选择?pytest和unittest哪个更强大pytest和unittest是否可同时应用如何选择? pytest和unittest都是Python中常用的测试框架,它们各自具有一些特点和优势,选择哪一个取决于你的具体需求和偏好。以下是一些关于这两个框架的对比和选择建议: 易用性和简洁性: pytes…...
《QT实用小工具·四》屏幕拾色器
1、概述 源码放在文章末尾 该项目实现了屏幕拾色器的功能,可以根据鼠标指定的位置识别当前位置的颜色 项目功能包含: 鼠标按下实时采集鼠标处的颜色。 实时显示颜色值。 支持16进制格式和rgb格式。 实时显示预览颜色。 根据背景色自动计算合适的前景色…...
【Linux C | 多线程编程】线程的连接、分离,资源销毁情况
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 ⏰发布时间⏰:2024-04-01 1…...
怎么做网站导航条/电商网站卷烟订货流程
题目描述 请设计一个高效算法,再给定的字符串数组中,找到包含”Coder”的字符串(不区分大小写),并将其作为一个新的数组返回。结果字符串的顺序按照”Coder”出现的次数递减排列,若两个串中”Coder”出现的次数相同,则…...
网站建设人员叫什么科目/排名查询系统
核心 核心 java函数用C语言实现逻辑 好处 C代码效率高,在某些情况下更适合. java代码 代码 public class Test {public native void show();public static void main(String[] args){System.load("/path/to/Test.so");(new Test()).show();} }说明 show和普通函数一样…...
网站换稳定服务器/今天的新闻 联播最新消息
共 2462 字,读完需 4 分钟。腾讯前端技术大会(TFC)和全球技术领导力峰会(GTLC)都于上周闭幕,我翻看了下讲稿,有价值的参考还是不少。不过本周的重点推荐推荐还是 Coursera 上的《Learning How t…...
漯河市建设局网站/外链seo服务
最近忙于一个RF产品的量产程序开发,测试平台选用advantest V93K SMT8,使用WSRF板卡(最高支持6Gbps)做LTE RX/TX的RF测试。大部分RF测试项都是大家耳熟能详的,比如RX/TX gain,LO IPN,RX flatness,…...
网站横幅怎么做/什么叫做网络营销
Linux下Tomcat重新启动,及kill命令的使用 首先,进入Tomcat下的bin目录 cd /usr/local/tomcat/bin 使用Tomcat关闭命令 ./shutdown.sh 查看Tomcat是否以关闭 ps -ef|grep java 如果显示以下相似信息,说明Tomcat还没有关闭 root 7010 1 0 Apr19…...
加强网站信息怎么做/seo排名优化软件有
我们身边的电磁波越来越多了。随着无线路由器的普及,无线网络信号似乎每天24小时辐射着我们。那么我们不禁要问问,WIFI对人究竟有没有危害? 这个问题总是不断地被提及,简而言之,答案是不会。事实上,将您的问题换一个方…...