gRPC C++客户端流式上传需四步:ClientContext + Write() + WritesDone() + Finish();必须手动分块Write()(如512KB/块),单块不可超4MB,默认限制需通过ChannelArguments显式调大;服务端用ServerReader循环Read(),校验chunk并追加写入磁盘,不支持原生断点续传。

gRPC C++ 客户端如何发起流式上传(ClientStreaming)
直接用 ClientContext + Write() + WritesDone() + Finish() 四步走,不是“边读边发”就自动流式——必须手动分块 Write(),且每块不能超过 gRPC 默认 4MB 消息上限(否则 Write() 返回 false)。
常见错误是把整个文件 fread() 进内存再一次性 Write(),这既吃内存又超限。正确做法是开固定缓冲区(比如 512KB),循环 fread() → Write(),每次只传一块。
-
Write()返回false时,说明底层流已断或服务端拒绝接收,需立即终止并检查Finish().error_code() - 调用
WritesDone()后不能再Write(),否则触发断言崩溃(Debug 模式下) - 务必在
Write()前设置request.set_chunk(data, size),字段名必须与 .proto 中定义的bytes chunk = 1;严格一致
服务端如何安全接收大文件流(ServerStreaming?不,是 ClientStreaming)
服务端必须用 ServerReader<fileuploadrequest></fileuploadrequest>(不是 ServerReaderWriter),否则收不到客户端的多段写入。很多人误配成双向流,导致 Read() 只返回一次就结束。
关键点在于:服务端 Read() 是阻塞同步调用,必须在循环里反复调用直到返回 false,且每次都要检查 request.chunk().size() 是否为 0(空 chunk 表示客户端已调用 WritesDone())。
立即学习“C++免费学习笔记(深入)”;
- 不要在
Read()外层加超时逻辑(如deadline),否则可能中断长文件传输;应在ServerContext设置set_deadline()为合理值(如 30 分钟) - 每个
chunk写入磁盘前建议校验request.has_chunk(),避免空数据覆盖文件 - 用
std::ofstream以std::ios::binary | std::ios::app模式追加写,别用ios::trunc,否则后几块会清空前面内容
如何绕过 4MB 默认消息大小限制
客户端和服务端都必须显式调高最大接收/发送消息尺寸,否则 >4MB 的单次 Write() 直接失败,错误信息是 Received message larger than max (4194304 vs. 4194304)。
这不是改编译选项,而是运行时通过 ChannelArguments 和 ServerBuilder 设置:
grpc::ChannelArguments args; args.SetMaxReceiveMessageSize(-1); // -1 表示无限制(慎用) args.SetMaxSendMessageSize(16 * 1024 * 1024); // 发送侧设为 16MB
服务端同理,在 ServerBuilder 中调用 SetMaxSendMessageSize() 和 SetMaxReceiveMessageSize()。
- 设为
-1虽方便,但存在内存耗尽风险;推荐按业务最大文件预估(如 2GB 文件,分块 1MB,则单 chunk 不超 1MB,无需设 -1) - 客户端 channel 参数必须在构造
std::shared_ptr<channel></channel>时传入,之后无法修改 - 服务端参数必须在
builder.BuildAndStart()前设置,否则无效
为什么上传中途断连后无法续传
原生 gRPC ClientStreaming 不支持断点续传——它没有 offset、checksum 或 session id 等机制。一旦 TCP 断开,整个 stream 就失效,Finish() 返回 StatusCode::UNAVAILABLE 或 DEADLINE_EXCEEDED,且服务端已写入的部分不会自动回滚。
真要支持续传,必须在应用层实现:在 proto 中增加 int64 offset = 2; 和 bytes checksum = 3; 字段,服务端收到后先查目标文件当前长度,比对 offset,不匹配则拒绝该 chunk。
- 客户端需自行维护已成功上传的字节数,下次从该 offset 继续读文件
- 服务端写文件前用
lseek(fd, offset, SEEK_SET)或std::ofstream.seekp(offset)定位,再写入 - 注意并发上传同一文件名时的竞态:建议服务端用临时文件名接收,全部完成后再
rename()
流式上传本身不难,难的是边界情况——网络闪断、磁盘满、权限错、服务重启。这些地方没兜底,上传 2GB 文件到 99% 失败,用户只会骂你。


















