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,表示可用的硬币的面值,以及一个…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...