首页 > 编程语言 >【Websocket】解析帧frame.c源码分析

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

时间:2024-07-01 21:43:14浏览次数:1  
标签:Websocket frame wss WSS 源码 offset message payload

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                   3
      0 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 帧

标签:Websocket,frame,wss,WSS,源码,offset,message,payload
From: https://www.cnblogs.com/LiBlog--/p/18276856

相关文章

  • 基于Flask的学生宿舍管理系统(含源码、文档、PPT、配套开发软件、软件安装教程)
    该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功软件开发环境及开发工具:开发语言:python使用框架:Flask前端技术:JavaScript、VUE.js(2.X)、css3开发工具:pycharm、VisualStudioCode、HbuildX数据库:MySQL5.7.26(版本号)数据库管理工具:phps......
  • QT6.7.2 MSVC源码编译 静态库 动态库
    QT6.7.2MSVC源码编译静态库动态库也可以参考官方的文档https://doc.qt.io/qt-6/build-sources.html环境搭建为了操作更有可复制性,这里在虚拟机中采用全新安装的系统进行配置。系统镜像为:en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96_2.iso安装VisualStudio......
  • C/C++ Dijkstra(迪杰斯特拉)算法详解及源码
    Dijkstra(迪杰斯特拉)算法是一种用于寻找带权重图中的最短路径的算法。它由荷兰计算机科学家EdsgerDijkstra于1956年提出,被广泛应用于网络路由算法和地图路线规划等领域。算法思想:初始化一个距离数组,用于保存起点到每个顶点的当前最短距离(初始时将起点距离设置为0,其他顶......
  • JDK动态代理方法Proxy.newProxyInstance源码分析
    JDK动态代理方法Proxy.newProxyInstance源码分析publicstaticObjectnewProxyInstance(ClassLoaderloader,Class<?>[]interfaces,InvocationHandlerh)方法入参解释:ClassLoaderloader:表示对应类加载器,用于加载对应代理类。Class<?>[]interfaces:表示一个接......
  • 短视频矩阵系统搭建教程,短视频矩阵怎么做,矩阵系统源码部署教程
    一、什么是矩阵系统这是一款智能助手系统,融合了账号授权管理、企业账户管理、AI素材库、视频剪辑创作、自动化回复响应、外部链接引流以及视频排名追踪等多重功能。简言之,这是一个助力企业提升短视频营销效果的智能助手平台。系统搭建获取\/:ywxs5787   备注来意二、矩......
  • [开源分享]好用的在线客服系统 PHP客服系统源码 聊天源码(开源代码+终身使用+安装教程
    源码介绍PHP在线客服系统源码采用全新UI,重新设计前端界面,后台采用php+mysql,免费开源源码。在线客服系统已成为企业与客户之间沟通的重要渠道。通过在线客服系统,企业可以方便地与客户进行实时沟通和解决问题,提升客户满意度。php客服系統源码主要功能要求:全新UI自动回复和机器......
  • ORB-SLAM3 源码分析
    一、ORB-SLAM3介绍ORB-SLAM3是一个先进的同时定位与地图构建(SimultaneousLocalizationandMapping,SLAM)系统,实现了基于视觉惯导紧耦合,同时能够对多地图进行复用;另外支持单目/双目/RGB-D作为输入,支持针孔以及鱼眼相机模型。是目前种类最齐全、工程化最好、精度和鲁棒性整体最佳的......
  • springboot校企对接实习管理系统 毕业设计-附源码11959
    摘 要校企合作实习是一种重要的实践教学模式,但是在实际的推行过程中,存在许多管理问题。其中包括远程指导困难、学生管理困难、校企信息沟通不畅等问题一直困扰着校方负责管理实习的教师们。随着互联网系统开发技术的发展,应用web技术开发B/s模式的实习管理系统,根据用户需......
  • Java计算机毕业设计粮库商品管理系统(开题+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着农业现代化和粮食流通体系的不断完善,粮库商品管理面临着日益复杂的挑战。传统的粮库管理方式往往依赖于人工记录和纸质文档,效率低下且易出错。同......
  • Java计算机毕业设计农资网络销售系统(开题+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的迅猛发展和农业现代化的推进,农资行业正面临转型升级的重大机遇。传统的农资销售模式往往依赖于实体店面和线下交易,效率低下且成本高昂......