C语言BSD套接字编程中如何用recv()接收websocket分块数据
WebSocket帧分片接收的逻辑实现方案
TCP是面向流的传输协议,本身不存在数据帧边界,你之前单次recv就能拿到完整WebSocket帧属于巧合,不具备通用性,当前遇到的服务器分片场景才是TCP传输的常态,需要从以下几个维度改造接收逻辑:
1. 新增累积接收缓冲区
你需要维护一个上下文级别的接收缓冲区,用于暂存未解析的不完整数据,建议封装成专用结构:
typedef struct { uint8_t *buf; size_t used_len; // 已存储的有效数据长度 size_t total_cap; // 缓冲区总容量 } ws_recv_buf_t; // 自行实现缓冲区初始化、扩容、数据追加的基础辅助函数即可
2. 实现完整帧长度判断逻辑
根据WebSocket协议规范,从已接收的数据中计算当前帧的总长度:
- 首先需要至少2字节数据,才能读取基础头部的payload长度标识位
- 若payload长度标识为
126:后续2字节为真实payload长度,头部总长度为4字节 - 若payload长度标识为
127:后续8字节为真实payload长度,头部总长度为10字节 - 再读取头部的MASK标识位,若为1则头部还要追加4字节的掩码key
- 完整帧总长度 = 头部总长度 + payload真实长度
3. 调整接收-解析主循环
主逻辑调整为「接收数据追加到缓冲区 -> 循环判断缓冲区是否存在完整帧 -> 解析完整帧后裁剪缓冲区 -> 继续接收」的流程,参考代码如下:
ws_recv_buf_t recv_buf = {0}; // 初始化缓冲区,初始容量可设为4096,不够时自动扩容即可 recv_buf.buf = malloc(4096); recv_buf.total_cap = 4096; recv_buf.used_len = 0; while(1) { // 每次recv往缓冲区的剩余空闲空间写入新数据 ssize_t ws_recv_ret = recv(*ws_sock, recv_buf.buf + recv_buf.used_len, recv_buf.total_cap - recv_buf.used_len, 0); if (ws_recv_ret <= 0) { // 此处自行处理连接关闭、出错逻辑,非阻塞模式下EAGAIN/EWOULDBLOCK属于正常状态,跳过继续循环即可 break; } recv_buf.used_len += ws_recv_ret; // 循环解析当前缓冲区内所有的完整帧 while(1) { size_t frame_total_len = 0; // 数据不足2字节,无法解析基础头部,退出等待更多数据 if (recv_buf.used_len < 2) { break; } // 计算头部长度和帧总长度 uint8_t payload_len_flag = recv_buf.buf[1] & 0x7F; size_t header_len = 2; if (payload_len_flag == 126) { if (recv_buf.used_len < 4) break; header_len += 2; uint16_t payload_len = ntohs(*(uint16_t*)(recv_buf.buf + 2)); frame_total_len = header_len + payload_len; } else if (payload_len_flag == 127) { if (recv_buf.used_len < 10) break; header_len += 8; uint64_t payload_len = be64toh(*(uint64_t*)(recv_buf.buf + 2)); frame_total_len = header_len + payload_len; } else { frame_total_len = header_len + payload_len_flag; } // 处理掩码位,增加头部长度 if (recv_buf.buf[1] & 0x80) { header_len += 4; frame_total_len += 4; } // 当前缓冲区数据不足一个完整帧,退出等待更多数据 if (recv_buf.used_len < frame_total_len) { break; } // 解析完整帧,注意此处需要给你的ws_parser_execute新增长度入参,明确传入本次解析的帧长度 ws_parser_execute(&ws_frame_parser1, recv_buf.buf, frame_total_len); // 裁剪缓冲区,把剩余未解析的数据移到缓冲区开头 size_t remain_len = recv_buf.used_len - frame_total_len; if (remain_len > 0) { memmove(recv_buf.buf, recv_buf.buf + frame_total_len, remain_len); } recv_buf.used_len = remain_len; } }
4. 边界注意事项
- 注意处理大帧场景,缓冲区要支持动态扩容,避免超过初始容量导致溢出
- 解析帧长度时要注意网络字节序转主机字节序,避免大小端解析错误
- 单个TCP包可能包含多个完整WebSocket帧,上述循环解析逻辑已经覆盖该场景
内容的提问来源于stack exchange,提问作者suriya meras
相关产品推荐
相关产品推荐

