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

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 模块

首发原文链接&#xff1a;Swoole 源码分析之 WebSocket 模块 大家好&#xff0c;我是码农先森。 引言 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时数据传输。 与传统的 HTTP 请求-响应模型不同&#xff0c;WebSocket 可以保持…...

Vue--------父子/兄弟组件传值

父子组件 子组件通过 props 属性来接受父组件的数据&#xff0c;然后父组件在子组件上注册监听事件&#xff0c;子组件通过 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…...

淘宝扭蛋机小程序:趣味购物新体验,惊喜连连等你来

在数字化时代&#xff0c;淘宝始终站在创新的前沿&#xff0c;不断探索和引领电商行业的发展趋势。今天&#xff0c;我们欣然宣布&#xff0c;经过精心研发和打磨&#xff0c;淘宝扭蛋机小程序正式上线&#xff0c;为用户带来一场充满趣味与惊喜的购物新体验。 淘宝扭蛋机小程…...

linux:生产者消费者模型

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、生产者消费者模型二、基于阻塞队列的生产者消费者模型代码实现 总结 前言 本文是对于生产者消费者模型的知识总结 一、生产者消费者模型 生产者消费者模型就是…...

C++教学——从入门到精通 5.单精度实数float

众所周知&#xff0c;三角形的面积公式是(底*高)/2 那就来做个三角形面积计算器吧 到吗如下 #include"bits/stdc.h" using namespace std; int main(){int a,b;cin>>a>>b;cout<<(a*b)/2; } 这不对呀&#xff0c;明明是7.5而他却是7&#xff0c;…...

面向对象设计之单一职责原则

设计模式专栏&#xff1a;http://t.csdnimg.cn/6sBRl 目录 1.单一职责原则的定义和解读 2.如何判断类的职责是否单一 3.类的职责是否越细化越好 4.总结 1.单一职责原则的定义和解读 单一职责原则(Single Responsibility Principle&#xff0c;SRP)的描述&#xff1a;一个类…...

蓝桥杯真题:单词分析

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开发技术

寻找有志同道合的小伙伴&#xff0c;互帮互助,群里还有不错的视频学习教程和PDF电子书&#xff01; ‘’’ 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、查询指定目录下所有的指定格式文件&#xff08;比如PDF文件&#xff09; find…...

黑马点评项目笔记 II

基于Stream的消息队列 stream是一种数据类型&#xff0c;可以实现一个功能非常完善的消息队列 key&#xff1a;队列名称 nomkstream&#xff1a;如果队列不存在是否自动创建&#xff0c;默认创建 maxlen/minid&#xff1a;设置消息队列的最大消息数量 *|ID 唯一id&#xff1a;…...

关于一篇知乎答案的重现

〇、前言 早上在逛知乎的时候&#xff0c;瞥见了一篇答案&#xff1a;如何通俗解释Docker是什么&#xff1f;感觉很不错&#xff0c;然后就耐着性子看了下&#xff0c;并重现了作者的整个过程。但是并不顺利&#xff0c;记载一下这些坑。嫌麻烦的话可以直接clone 研究&#xf…...

实时数据库测试-汇编小程序

实时数据库测试-汇编小程序。 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. 新的语义化元素&#xff1a;article 、footer 、header 、nav 、section 2. 表单增强&#xff0c;新的表单控件&#xff1a;calendar 、date 、time 、email 、url 、search 3. 新的 API&#xff1a;音频(用于媒介回放的 video 和 audio 元素)、图形&#x…...

基于springboot+vue实现的驾校信息管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…...

X进制减法(贪心算法C++实现)

题目 进制规定了数字在数位上逢几进一。 X 进制是一种很神奇的进制&#xff0c;因为其每一数位的进制并不固定&#xff01; 例如说某种 X 进制数&#xff0c;最低数位为二进制&#xff0c;第二数位为十进制&#xff0c;第三数位为八进制&#xff0c;则 X 进制数 321 转换为十…...

[Windows]服务注册工具(nssm)

文章目录 官网下载地址百度云下载地址NSSM常用命令 使用场景&#xff1a;例如现在我们想开启自动启动一个Java服务,nginx,node等。 官网下载地址 https://nssm.cc/download 百度云下载地址 链接&#xff1a;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. 需要添加的硬币的最小数量 题目描述&#xff1a; 实现代码与解析&#xff1a; 贪心 原理思路&#xff1a; 2952. 需要添加的硬币的最小数量 题目描述&#xff1a; 给你一个下标从 0 开始的整数数组 coins&#xff0c;表示可用的硬币的面值&#xff0c;以及一个…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

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

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

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...