You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 17:54:03