【Websocket】解析帧frame.c源码分析

news/2024/7/3 10:56:16/文章来源:https://www.cnblogs.com/LiBlog--/p/18276856

0. 简介

本文主要分析 https://github.com/mortzdk/websocket中解析帧相关函数

1. predict.h

#ifndef wss_predict_h
#define wss_predict_h#if defined(__GNUC__ ) || defined(__INTEL_COMPILER)
/*__builtin_expect 是 GCC 提供的一个内建函数,用于向编译器提示某个条件在大多数情况下是否为真*/
#define likely(x)      __builtin_expect(!!(x), 1)  /*!!(x) 将 x 转换为布尔值(0 或 1),条件很可能为真*/
#define unlikely(x)    __builtin_expect(!!(x), 0)#else
#define likely(x)      (x)
#define unlikely(x)    (x)#endif#endif

2. frame.c

      0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-------+-+-------------+-------------------------------+|F|R|R|R| opcode|M| Payload len |    Extended payload length    ||I|S|S|S|  (4)  |A|     (7)     |             (16/64)           ||N|V|V|V|       |S|             |   (if payload len==126/127)   || |1|2|3|       |K|             |                               |+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +|     Extended payload length continued, if payload len == 127  |+ - - - - - - - - - - - - - - - +-------------------------------+|                               |Masking-key, if MASK set to 1  |+-------------------------------+-------------------------------+| Masking-key (continued)       |          Payload Data         |+-------------------------------- - - - - - - - - - - - - - - - +:                     Payload Data continued ...                :+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +|                     Payload Data continued ...                |+---------------------------------------------------------------+
| Frame Header | Extension Data | Application Data |
|--------------|----------------|------------------|
|    2 bytes   |                |                  |

帧头部(Frame Header):

  • FIN, RSV1, RSV2, RSV3, Opcode, Mask, Payload Length

扩展数据(Extension Data)(如果有):

  • 根据扩展协议的定义,可以有任意长度。
  • 扩展数据的长度必须在帧头信息中进行描述,并且在应用数据之前

应用数据(Application Data):

  • 实际需要传输的数据。
  • 应用数据的长度由帧头中的 payload length 减去扩展数据的长度来确定

2.1 解析 WebSocket 帧

wss_frame_t *WSS_parse_frame(char *payload, size_t length, size_t *offset);功能:从给定的 payload 数据中解析出一个 WebSocket 帧参数:payload:指向数据的指针。length:是数据的长度。offset:是当前解析数据的偏移量,初始值应为0,并在函数内更新。返回值:成功:解析后的frame,一个wss_frame_t结构体类型的指针失败:NULL
/*解析帧的第一个字节,WebSocket 帧的控制位(FIN、RSV1、RSV2、RSV3)和操作码(opcode)*/
frame->fin    = 0x80 & payload[*offset];  //10000000 & payload,只对字节的最高位
frame->rsv1   = 0x40 & payload[*offset];
frame->rsv2   = 0x20 & payload[*offset];
frame->rsv3   = 0x10 & payload[*offset];
frame->opcode = 0x0F & payload[*offset];  //最低4位,即第0到第3位*offset += 1;  //偏移量增加1,解析帧的下一个字节/*解析第二个字节*/
if ( likely(*offset < length) ) 
{frame->mask          = 0x80 & payload[*offset];frame->payloadLength = 0x7F & payload[*offset];
}
*offset += 1;/**payload length,masking-key,payload数据的提取(对照着websocket数据格式)*......*/

2.2 转换一个帧

size_t WSS_stringify_frame(wss_frame_t *frame, char **message);功能:将一个 WebSocket 帧(wss_frame_t)转换为一个字节数组(char array)参数:frame:指向 wss_frame_t 结构的指针,表示需要转换的 WebSocket 帧。message:指向 char 数组的指针的指针,转换后的帧数据将存储在这里。返回值:成功:帧数据的总长度失败:0
/*根据 payload 的长度决定是否需要额外的 2 字节或 8 字节来表示长度(对照websocket数据格式)*/
if ( likely(frame->payloadLength > 125) ) 
{if ( likely(frame->payloadLength <= 65535) ) {len += sizeof(uint16_t);} else {len += sizeof(uint64_t);}
}len += frame->payloadLength;
/*设置第一个字节*/
if (frame->fin) {mes[offset] |= 0x80;
}if (frame->rsv1) {mes[offset] |= 0x40;
}if ( unlikely(frame->rsv2) ) {mes[offset] |= 0x20;
}if ( unlikely(frame->rsv3) ) {mes[offset] |= 0x10;
}mes[offset++] |= 0xF & frame->opcode;/*设置Payload length 字段*/
if ( unlikely(frame->payloadLength <= 125) ) {mes[offset++] = frame->payloadLength;
} else if ( likely(frame->payloadLength <= 65535) ) {uint16_t plen;mes[offset++] = 126;plen = htons16(frame->payloadLength);memcpy(mes+offset, &plen, sizeof(plen));offset += sizeof(plen);
} else {uint64_t plen;mes[offset++] = 127;plen = htonl64(frame->payloadLength);memcpy(mes+offset, &plen, sizeof(plen));offset += sizeof(plen);
}/*扩展数据和应用数据*/
if ( unlikely(frame->extensionDataLength > 0) ) {memcpy(mes+offset, frame->payload, frame->extensionDataLength);offset += frame->extensionDataLength;
}if ( likely(frame->applicationDataLength > 0) ) {memcpy(mes+offset, frame->payload+frame->extensionDataLength, frame->applicationDataLength);offset += frame->applicationDataLength;
}

2.3 转换多个帧

size_t WSS_stringify_frames(wss_frame_t **frames, size_t size, char **message);功能:将多个 WebSocket 帧转换为一个连续的字节数组参数:frames:指向 wss_frame_t 结构数组的指针,表示需要转换的多个 WebSocket 帧。size:帧的数量message:指向 char 数组的指针的指针,转换后的帧数据将存储在这里返回值:成功:消息的总长度失败:0
for (i = 0; likely(i < size); i++) {/*遍历每个帧并调用 WSS_stringify_frame 函数将其转换为字节数组。*/n = WSS_stringify_frame(frames[i], &f);/*如果接收到的字节数小于 2,说明帧无效,记录错误日志,释放已分配的内存并返回 0*/if ( unlikely(n < 2) ) {WSS_log_error("Received invalid frame");*message = NULL;WSS_free((void **)&f);WSS_free((void **)&msg);return 0;}/*重新分配内存,以便将新的帧数据拼接到消息数组中*/if ( unlikely(NULL == (msg = WSS_realloc((void **) &msg, message_length*sizeof(char),(message_length+n+1)*sizeof(char)))) ) {WSS_log_error("Unable to allocate message string");*message = NULL;WSS_free((void **)&f);return 0;}/*将当前帧的字节数组复制到消息数组中,并更新message_length*/memcpy(msg+message_length, f, n);message_length += n;WSS_free((void **) &f);
}

为什么需要处理多个帧?

1、消息分片(Fragmentation)

  • WebSocket 协议允许将一条大的消息分成多个较小的帧进行传输。这样做的好处是可以控制每个帧的大小,以避免在传输大消息时一次性占用过多带宽或内存。
  • 处理多个帧意味着接收端需要将这些帧重新组装成一条完整的消息。

2、控制帧与数据帧的混合传输

  • WebSocket 协议定义了几种不同类型的帧(例如,文本帧、二进制帧、关闭帧、Ping 帧和 Pong 帧)。在一个 WebSocket 会话中,可能会同时传输多种类型的帧。
  • 处理多个帧使得应用程序能够处理控制帧(如 Ping/Pong)和数据帧(如文本和二进制数据)之间的交互。

3、流控制与高效传输

  • 通过分片,可以更有效地实现流控制。如果某个帧丢失,只需要重传丢失的帧,而不是重传整个消息。
  • 在实时通信场景中,小帧的传输和处理延迟通常较低,可以提高实时性和响应速度。

4、错误处理和恢复

  • 如果单个大帧在传输过程中出现错误,可能会导致整个消息无法恢复。但如果消息被分成多个小帧传输,即使某个帧出现错误,也可以通过重传该帧来恢复整个消息。
  • 多帧处理可以检测和处理错误。

2.4 将消息转换为帧

size_t WSS_create_frames(wss_config_t *config, wss_opcode_t opcode, char *message, size_t message_length, wss_frame_t ***fs) ;功能:将消息转换为多个 WebSocket 帧参数:config:服务器配置,包含每帧的最大大小等参数。opcode:帧的操作码,指示帧的类型(如文本帧、二进制帧、关闭帧等)。message:要转换为帧的消息。message_length:消息的长度。fs:指向 wss_frame_t 结构数组的指针的指针,存储创建的帧。返回值:成功:创建的帧的数量失败:0
/**处理关闭帧。如果操作码是关闭帧,则创建关闭帧,并返回 1。*......*//*根据消息长度和每帧最大大小,循环创建帧*/
for (i = 0; i < frames_count; i++) {if ( unlikely(NULL == (frame = WSS_malloc(sizeof(wss_frame_t)))) ) {WSS_log_error("Unable to allocate frame");for (j = 0; j < i; j++) {WSS_free_frame(frames[j]);}WSS_free((void **)&frames);*fs = NULL;return 0;}frame->fin = 0;frame->opcode = opcode;frame->mask = 0;frame->applicationDataLength = MIN(message_length-(config->size_frame*i), config->size_frame);  //计算并设置每个帧的应用数据长度if ( unlikely(NULL == (frame->payload = WSS_malloc(frame->applicationDataLength+1))) ) {WSS_log_error("Unable to allocate frame application data");for (j = 0; j < i; j++) {WSS_free_frame(frames[j]);}WSS_free((void **)&frame);WSS_free((void **)&frames);*fs = NULL;return 0;}memcpy(frame->payload, msg+offset, frame->applicationDataLength);  //将消息数据复制到帧的负载中frame->payloadLength += frame->extensionDataLength;frame->payloadLength += frame->applicationDataLength;offset += frame->payloadLength;frames[i] = frame; //将帧添加到帧数组中
}frames[frames_count-1]->fin = 1; //将最后一个帧的 fin 标志设置为 1,表示消息结束

2.4 关闭帧

wss_frame_t *WSS_closing_frame(wss_close_t reason, char *message);功能:根据关闭原因创建一个 WebSocket 关闭帧参数:reason:关闭的原因,类型为 wss_close_t,枚举值表示不同的关闭原因。message:关闭帧的附加消息,类型为 char*,可以为空。返回值:成功:创建的 WebSocket 关闭帧失败:NULL
/***根据关闭原因枚举值设置对应的默认消息。*......*//*计算应用数据长度,应用数据长度等于关闭原因字符串的长度加上 2个字节(用于存储关闭状态码)。*/frame->applicationDataLength = strlen(reason_str)+sizeof(uint16_t);if ( unlikely(NULL == (frame->payload = WSS_malloc(frame->applicationDataLength+1))) ) {WSS_log_error("Unable to allocate closing frame application data");WSS_free_frame(frame);return NULL;}nbo_reason = htons16(reason);memcpy(frame->payload, &nbo_reason, sizeof(uint16_t));memcpy(frame->payload+sizeof(uint16_t), reason_str, strlen(reason_str));frame->payloadLength += frame->extensionDataLength;frame->payloadLength += frame->applicationDataLength;return frame;

2.5 PING帧

wss_frame_t *WSS_ping_frame();功能:创建一个PING帧作为心跳消息返回值:成功:创建的 WebSocket PING 帧失败:NULL
    frame->fin = 1;  //设置为 1,表示这是一个完整的帧frame->opcode = PING_FRAME;  //设置为 PING_FRAME,表示这是一个 PING 帧frame->mask = 0; //设置为 0,表示不使用掩码/*设置 PING 帧的负载数据*/frame->applicationDataLength = 120; //长度,设置为 120 字节if ( unlikely(NULL == (frame->payload = random_bytes(frame->applicationDataLength))) ) { //生成 120 字节的随机数据作为负载数据WSS_log_error("Unable to allocate ping frame application data");WSS_free_frame(frame);return NULL;}

2.6 PONG帧

wss_frame_t *WSS_pong_frame();功能:将接收到的 PING 帧转换为 PONG 帧返回值:成功:创建的 WebSocket PONG 帧失败:NULL
    ping->fin = 1;/***rsv1,rsv2和rsv3位在没有扩展时应该保持为0,适用于所有帧类型*在这里显示的设置保留位rsv,主要是为了确保在处理 PING 帧转换为 PONG 帧时,保留位保持为 0,确保符合协议规范*/ping->rsv1 = 0;ping->rsv2 = 0;ping->rsv3 = 0;ping->opcode = PONG_FRAME;ping->mask = 0;memset(ping->maskingKey, '\0', sizeof(uint32_t));

2.7 PING和PONG的作用和区别

特性 PING 帧 PONG 帧
作用 发送以检查连接状态和保持连接活跃 响应 PING 帧并保持连接活跃
发送方 客户端或服务器 接收 PING 帧的一方(客户端或服务器)
负载数据 可以为空或随机数据 通常匹配 PING 帧的负载数据
主要用途 连接检查、心跳机制、保持连接活跃 确认连接活跃、响应 PING 帧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/735998.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玄机-第一章 应急响应-Linux日志分析

玄机-第一章 应急响应-Linux日志分析 账号root密码linuxrz ssh root@IP 1.有多少IP在爆破主机ssh的root帐号,如果有多个使用","分割 2.ssh爆破成功登陆的IP是多少,如果有多个使用","分割 3.爆破用户名字典是什么?如果有多个使用","分割 4.登陆…

使用 .NET 构建 UI 界面的各种方式

微软搞出了很多构建 UI 程序的框架,如 WinForms WPF WinUI MAUI,他们之间的简单对比可以看如下这篇官方文档 Overview of framework options - Windows apps | Microsoft Learn 本文主要是记录一下在搜索相关问题时,了解到的内容,不一定准确,如果发现错误,请留言补充。 1…

Docker详细安装教程

安装Docker: # 1,, 卸载旧的版本# 2,需要的安装 yum install -y yum-utils# 3, 设置镜像的仓库 https://blog.csdn.net/qq_43168442/article/details/116770163 (访问这个博客网站进行配置)# 更新yum软件包索引 yum makecache fast# 4,安装docker docker-ce 社区 ee企业…

详细讲解 Keil Pack Installer,以及通过 Keil 官网获取 Pack

前言 大家好,我是梁国庆。 收到粉丝留言,说 Keil 安装 Pack 不太明白,可不可以详细演示一下?当然可以有,直接视频+文章全部安排,我就是宠粉。 PS:第一次录视频有些紧张,见谅哈。微信视频号:https://weixin.qq.com/sph/AXbpYwEaw b站:https://www.bilibili.com/video…

webdav协议及我的笔记方案(私有部署)

背景 用markdown用于文章写作,有几年时间了,不是很喜欢折腾,主要就是在电脑上写,用的笔记软件就是typora。由于里面有很多工作相关的,以及个人资料相关的(包含了各种账号、密码啥的),所以不敢往各种云服务上放,还是想着数据由自己来管着。 自己管数据的话,就是数据存…

模拟集成电路设计系列博客——8.3.2 PLL中的抖动与相位噪声

8.3.2 PLL中的抖动与相位噪声 在PLL中有若干种抖动源,具体来说包括:输入参考的抖动\(\phi_{in}\) VCO中的抖动 环路滤波器产生的噪声 分频器产生的噪声由于任何实际PLL中的抖动都相对较小,因此分析其在环路中和环路内的传播可以使用线性小信号模型。上面列出的噪声源出现在环…

【git】github如何上传超过100MB大小的单个文件

在使用 GitHub 进行版本控制时,默认情况下,单个文件的大小限制为 100MB。 如果你需要上传超过这个大小的文件,可以使用 Git LFS(Large File Storage)。 Git LFS 是一种 Git 扩展,专门用于处理大文件,它将大文件替换为轻量级的指针,并将实际的文件内容存储在远程服务器上…

ros2 - microros - 雷达 -可视化点云

上一节完成了指定角度距离的测量这一节我们将其合成ROS的laserscan消息,并将其通过microros发布到上位机,最终实现rviz2的可视化。 一、雷达消息介绍使用指令ros2 interface show sensor_msgs/msg/LaserScan,可以看到ROS2对雷达数据接口的定义。# Single scan from a planar…

玄机流量特征分析-蚁剑流量分析

玄机流量特征分析-蚁剑流量分析 1.木马的连接密码是多少 2.黑客执行的第一个命令是什么 3.黑客读取了哪个文件的内容,提交文件绝对路径 4.黑客上传了什么文件到服务器,提交文件名 5.黑客上传的文件内容是什么 6.黑客下载了哪个文件,提交文件绝对路径1.过滤http,发现连接密码…

F407在RAM中调试

在RAM中调试代码 一.软硬件操作 硬件方面需要先修改BOOT0和BOOT1的引脚的高低电平。SRAM运行模式BOOT0和BOOT1都是高电平。软件方面: 1.需要先取消勾选Use Memory Layout from Target Dialog,点击下方的Edit编辑.sct2.修改.sct分散加载文件 原始文件 LR_IROM1 0x08000000 0x0…

制作badusb上线CS

‍ 前言在2014年美国黑帽大会上,安全研究人员JakobLell和独立安全研究人员Karsten Nohl展示了他们称为“BadUSB”的攻击方法,这种攻击方法让USB安全和几乎所有和USB相关的设备(包括具有USB端口的电脑)都陷入相当危险的状态现在的USB设备很多,比如语音视频设备、摄像头等,因…

【数据结构】常见的几种数据结构

常见的数据结构:数组、链表、队列、栈、、堆、二叉树、B树、哈希表、图 数组 因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来。根据索引查找元素,时间复杂度是 \(O(1)\)。 动态数组动态数组具体代码实现 import java.util.Arrays; import java…

QT6.7.2 MSVC源码编译 静态库 动态库

QT6.7.2 MSVC源码编译 静态库 动态库 也可以参考官方的文档 https://doc.qt.io/qt-6/build-sources.html 环境搭建 为了操作更有可复制性,这里在虚拟机中采用全新安装的系统进行配置。 系统镜像为:en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96_2.iso 安装 Visual…

CPU管理 多进程图像

CPU管理 && 多进程图像 要管理CPU,先要学会使用CPU CPU的工作方式在操作系统学习之初就已经提过:取值执行程序存放在内存中,每段指令对应一个地址 CPU发出取指命令,将想取地址通过地址总线传到PC 内存根据地址取出对应地址的指令 从总线传回,CPU解释执行总之,就是…

电子显微镜对生物的观察

某黄色小虫,大概 \(100μm \times 400μm\) 的样子。

ARM Cortex-A 与 STM32 F107

ARM Cortex-A 和 STM32 F107 是两种不同的微控制器架构,它们在性能、应用场景和硬件支持方面有很大的差异。以下是它们的详细比较和分析: 1. 基本特性比较ARM Cortex-A 系列ARM Cortex-A 是 ARM 架构中的高级应用处理器系列,专为复杂的计算任务和高级应用设计。它广泛用于智…

前端开发 | Node 版本管理器选择

一、Nvm nvm 是一款 Node.js 版本管理工具,允许用户通过命令行快速安装、切换和管理不同的 Node.js 版本。nvm 只适用于 macOS 和 Linux 用户的项目,如果是 Windows 用户,可以使用 nvm-windows 、nodist或 nvs 替换。 安装方式 macOS 下载方式: brew install nvm # or sh 命…

示波器基本使用方法

示波器 示波器的基本使用方法首先检查示波器包装中是否有下列物品:• 示波器。 • 电源线。 • N2841A 10:1 10 MΩ 无源探头,数量= 2连接电源和信号源:将示波器的电源插入电源插座,并确保电源适配器或电池充足。 将需要测量的信号源(例如电路板、信号发生器等)连接到示波…

ros - Adafruit ESP32 Feather与树莓派(Raspberry Pi)比较

Adafruit ESP32 Feather 和树莓派(Raspberry Pi)都是广泛使用的硬件平台,分别在嵌入式系统和单板计算机领域具有各自的优势。以下是它们的详细比较,以及它们对机器人操作系统(ROS)的支持程度的分析。 1. 基本特性比较Adafruit ESP32 Feather核心处理器: 双核 Xtensa LX6(…