应直接发送 raw buffer(如 std::vector 或裸指针+长度),避免转字符串或 base64;需处理 send() 部分发送、tcp 粘包(加网络字节序长度头)、禁用 std::string 存二进制数据。

直接发 std::vector<uint8_t></uint8_t> 或裸指针 + 长度就行,别转成字符串或 base64 —— 那会破坏二进制语义,服务端收不到原始字节。
用 send() 发送 raw buffer 最简路径
Linux/macOS 下用原生 send(),Windows 用 send()(需先调用 WSAStartup());核心就是传入内存起始地址和确切字节数:
std::vector<uint8_t> data = read_binary_file("payload.bin"); // 假设已读好
ssize_t sent = send(sockfd, data.data(), data.size(), 0);
if (sent != static_cast<ssize_t>(data.size())) {
// 处理错误:可能是 EAGAIN/EWOULDBLOCK(非阻塞 socket),或连接断开
}</ssize_t></uint8_t>
-
send()不保证一次发完全部字节,返回值可能小于data.size(),必须检查并循环补发 - 若 socket 是阻塞模式,
send()会等缓冲区有空间;但网络卡顿或对端接收慢时仍可能只发部分 - 不要用
sizeof(data)—— 那是 vector 对象大小,不是数据长度
发送前必须处理的粘包与长度标记
TCP 是字节流,没有消息边界。服务端无法知道哪几个字节是一次完整的“二进制块”。常见做法是在数据前加一个固定长度的长度头(如 4 字节小端整数):
uint32_t len = htonl(static_cast<uint32_t>(data.size())); // 网络字节序 send(sockfd, &len, sizeof(len), 0); // 先发长度 send(sockfd, data.data(), data.size(), 0); // 再发内容</uint32_t>
- 必须用
htonl()或htons()转网络字节序,否则 x86 和 ARM 服务端解析会错 - 服务端需先收够 4 字节,解析出长度
n,再收n字节 —— 否则直接 recv 可能截断或混入下一条 - 别用分隔符(如 \0)切二进制数据:原始数据里完全可能出现该字节
避免 std::string 承载二进制数据的坑
很多人图方便用 std::string 存二进制,但隐患明显:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
std::string构造函数遇到\0会截断(如string(buf, len)是安全的,但string(buf)不是) -
string::c_str()返回以\0结尾的指针,但二进制数据中\0是合法内容,调用strlen()类函数会误判长度 - 某些旧库(如 Boost.ASIO 的某些重载)会对
string做隐式 null 截断假设
结论:二进制就用 std::vector<uint8_t></uint8_t> 或 std::span<const uint8_t></const>(C++20),别碰 std::string。
大文件发送要分块 + 检查返回值
单次 send() 通常受限于 socket 缓冲区(几 KB 到几 MB),超长数据必须拆:
size_t offset = 0; while (offset
- 每次发完必须更新
offset和剩余长度,不能硬写send(..., data.size(), ...) - 生产环境建议加超时控制(如
select()或poll()判断 socket 可写),避免无限阻塞 - 若需可靠性,应用层加简单校验(如发送后等待服务端回一个
ACK包)
真正容易被忽略的是:长度头和 payload 必须原子发送(至少逻辑上),否则服务端在收长度头中途断连,就会彻底失同步 —— 这类问题在线上很难复现,但一出就是批量解析失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










