WebSocket 协议、帧结构与 MTU 详解

张开发
2026/4/13 17:43:19 15 分钟阅读

分享文章

WebSocket 协议、帧结构与 MTU 详解
WebSocket 协议概述WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许客户端和服务器之间进行实时、双向的数据传输。协议特点全双工通信客户端和服务器可以同时发送和接收数据基于 TCP建立在可靠的 TCP 连接之上帧格式传输数据以帧Frame为单位进行传输支持消息分片大消息可以被拆分成多个帧WebSocket 帧结构帧格式WebSocket 帧由以下部分组成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 len126/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 ... | ---------------------------------------------------------------帧头字段说明字段位数说明FIN1 bit标识是否为消息的最后一个片段。1 最后片段0 还有后续片段RSV1-33 bits保留字段必须为 0Opcode4 bits帧类型0x0 延续帧0x1 文本帧0x2 二进制帧0x8 连接关闭0x9 ping0xA pongMASK1 bit是否使用掩码。客户端到服务器必须为 1服务器到客户端为 0Payload Length7 bits载荷长度0-125 实际长度126 后续 2 字节表示长度127 后续 8 字节表示长度Masking-key0/4 bytes如果 MASK1则包含 4 字节掩码键Payload Data可变实际数据载荷帧类型示例单帧消息小消息FIN1, Opcode0x1, PayloadHello一条完整的文本消息在一个帧中传输多帧消息大消息帧1: FIN0, Opcode0x1, Payload前3KB数据 帧2: FIN0, Opcode0x0, Payload中间4KB数据 (延续帧) 帧3: FIN1, Opcode0x0, Payload后3KB数据 (延续帧)第一条帧的 Opcode 表示消息类型文本/二进制后续帧的 Opcode 必须为 0x0延续帧只有最后一个帧的 FIN1MTU 与网络分层MTU 概念MTU (Maximum Transmission Unit)是网络层能够传输的最大数据包大小。以太网 MTU通常为 1500 字节实际 TCP 载荷MTU - IP 头(20字节) - TCP 头(20字节) 1460 字节网络协议栈分层┌─────────────────────────────────────┐ │ 应用层: WebSocket 消息 (10KB) │ ├─────────────────────────────────────┤ │ WebSocket 层: WebSocket 帧 │ ← 帧大小几KB到几十KB │ (受 libwebsockets 缓冲区限制) │ ├─────────────────────────────────────┤ │ TCP 层: TCP 段 │ ← 段大小受 TCP 发送缓冲区限制 │ (受 TCP 发送缓冲区限制) │ ├─────────────────────────────────────┤ │ IP 层: IP 数据包 │ ← 包大小受 MTU 限制 (1500字节) │ (根据 MTU 自动分片) │ ├─────────────────────────────────────┤ │ 以太网层: 以太网帧 │ └─────────────────────────────────────┘各层的作用WebSocket 层将消息拆分成帧处理协议逻辑TCP 层提供可靠传输流量控制拥塞控制IP 层根据 MTU 自动分片路由转发以太网层物理传输帧拆分与重组机制发送端自动拆分当发送大消息时libwebsockets 会自动将消息拆分成多个帧拆分依据主要因素libwebsockets 的内部缓冲区大小通常 4KB-64KB次要因素TCP 发送缓冲区大小不是直接按 MTU 拆分MTU 是 IP 层的事情WebSocket 层不关心拆分过程完整消息 (10KB) ↓ libwebsockets 按缓冲区大小拆分 ↓ 帧1: FIN0, Opcode0x1, Payload4KB 帧2: FIN0, Opcode0x0, Payload4KB (延续帧) 帧3: FIN1, Opcode0x0, Payload2KB (延续帧)接收端需要手动重组接收端需要根据 FIN 标志判断消息是否完整重组逻辑收到帧时检查FIN标志如果FIN0将 payload 累积到缓冲区如果FIN1合并所有片段得到完整消息代码实现参考CppWebSocket.cppcase LWS_CALLBACK_CLIENT_RECEIVE: bool is_final lws_is_final_fragment(wsi); // 累积当前片段 receiveBuffer.append((const char *)in, len); // 如果是最后一个片段处理完整消息 if (is_final) { std::string completeMsg std::move(receiveBuffer); receiveBuffer.clear(); callOnMessage(completeMsg); // 传递给上层 }libwebsockets 实现细节协议配置在CppWebSocket.cpp中的配置static struct lws_protocols protocols[] { { cpp-websocket-protocol, // 协议名称 CppWebSocket::Impl::lwsCallback, // 回调函数 sizeof(void *), // 每个连接的用户数据大小 4096, // rx_buffer_size: 接收缓冲区大小 0, // tx_packet_size: 发送包大小0自动 nullptr, // 协议特定数据 0 // 协议索引 } };关键参数说明参数值说明rx_buffer_size4096接收缓冲区大小影响每次LWS_CALLBACK_CLIENT_RECEIVE回调的最大数据量tx_packet_size0发送包大小0 表示由库自动决定通常也是几KB发送流程// 1. 用户调用 send() send(10KB完整消息); // 2. 消息加入队列 sendQueue.push(message); // 3. 触发写事件 lws_callback_on_writable(wsi); // 4. 在 WRITEABLE 回调中发送 lws_write(wsi, buf.data() LWS_PRE, msg.size(), LWS_WRITE_TEXT); // libwebsockets 会自动拆分帧并发送接收流程// 1. libwebsockets 解析帧头 // 2. 提取 payload 数据 // 3. 调用 LWS_CALLBACK_CLIENT_RECEIVE // 4. 用户代码检查 FIN 标志并重组消息实际传输流程示例场景发送一条 10KB 的消息第 1 层WebSocket 层应用层原始消息: 10KB 文本数据 ↓ libwebsockets 拆分按内部缓冲区假设 4KB WebSocket 帧1: FIN0, Opcode0x1, Payload4KB WebSocket 帧2: FIN0, Opcode0x0, Payload4KB WebSocket 帧3: FIN1, Opcode0x0, Payload2KB第 2 层TCP 层每个 WebSocket 帧 → TCP 段 TCP 段1: ~4KB (包含 WebSocket 帧1) TCP 段2: ~4KB (包含 WebSocket 帧2) TCP 段3: ~2KB (包含 WebSocket 帧3)第 3 层IP 层根据 MTU1500 字节分片TCP 段1 (4KB) → IP 数据包 IP 包1: 1460 字节 payload (1500 - 20 IP头 - 20 TCP头) IP 包2: 1460 字节 payload IP 包3: 1460 字节 payload IP 包4: 220 字节 payload TCP 段2 (4KB) → IP 数据包 IP 包5: 1460 字节 payload IP 包6: 1460 字节 payload IP 包7: 1460 字节 payload IP 包8: 220 字节 payload TCP 段3 (2KB) → IP 数据包 IP 包9: 1460 字节 payload IP 包10: 540 字节 payload接收端重组过程IP 层: 重组 IP 分片 → TCP 段 TCP 层: 重组 TCP 段 → WebSocket 帧 WebSocket 层: 根据 FIN 标志重组帧 → 完整消息 接收回调1: payload4KB, FIN0 → 累积到缓冲区 接收回调2: payload4KB, FIN0 → 累积到缓冲区 接收回调3: payload2KB, FIN1 → 合并完成得到 10KB 完整消息 ✓关键要点总结1. WebSocket 帧大小 ≠ MTUWebSocket 帧通常几 KB 到几十 KB由 libwebsockets 缓冲区决定MTU通常 1500 字节IP 层限制WebSocket 帧可以大于 MTUTCP/IP 层会自动分片2. 为什么不是直接按 MTU 拆分减少帧头开销WebSocket 帧头约 2-14 字节按 MTU 拆分会产生更多帧头提高效率较大的帧减少协议处理次数简化实现由 TCP/IP 层处理 MTU 分片应用层无需关心3. 发送端 vs 接收端端处理方式说明发送端自动处理libwebsockets 自动拆分帧用户只需调用lws_write()接收端手动重组需要根据 FIN 标志累积片段直到收到完整消息4. 实际配置建议rx_buffer_size: 根据应用场景调整4KB-64KB太小增加帧数量增加处理开销太大增加内存占用延迟增加保持默认值对于大多数应用4KB 是合理的默认值

更多文章