C#中Protobuf ToByteArray与WriteDelimitedTo的差异及报错解析
刚接触C#,需要通过Protobuf+WebSocket与Go实现的服务端通信。最初的实现代码如下:
var command = new Command { //some fields }.ToByteArray(); _websocketClient.SendAsync(command);
其中_websocketClient使用的是websocket-sharp客户端。但服务端无法反序列化请求字节,出现错误:
proto: Command: illegal tag 0 (wire type 1)
该错误来自Protobuf生成的代码,似乎发送的字节无效。随后切换为以下实现后一切正常:
using var ms = new MemoryStream(); command.WriteDelimitedTo(ms); _websocketClient.SendAsync(ms.GetBuffer());
问题显然源于序列化方式的差异,但未找到文档说明WriteDelimitedTo的作用及与ToByteArray()的区别。请解释二者差异及首次实现失败的原因。
1. ToByteArray()与WriteDelimitedTo()的核心差异
ToByteArray():直接把Protobuf对象序列化为原始二进制字节流,仅包含对象本身的Protobuf编码数据,没有任何额外前缀。WriteDelimitedTo():在对象的二进制编码数据前,会先写入一个Varint编码的长度前缀,用来标记后续对象数据的总字节数。
2. 首次实现失败的原因
你的Go服务端应该是用了带长度前缀的Protobuf解析逻辑(比如proto.UnmarshalDelimited方法),这种方式要求数据必须遵循「长度前缀+对象数据」的格式。
第一次用ToByteArray()发送的是纯对象编码字节,服务端解析时会误把对象第一个字段的Tag和Wire Type当成长度前缀来读取,直接打乱了解析逻辑,最终抛出illegal tag 0的错误——本质是服务端读错了数据结构,把原本属于对象的字段解析成了无效的标识。
而WriteDelimitedTo()补上了服务端需要的长度前缀,让服务端能先读取长度,再准确截取对应字节数的对象数据进行反序列化,所以通信恢复正常。
补充说明
Protobuf本身的二进制格式没有内置长度信息,在WebSocket、TCP这类流式通信场景下,必须通过长度前缀来划分消息边界——否则服务端无法判断当前收到的字节是一个完整对象、多个对象的拼接,还是某个对象的部分数据。WriteDelimitedTo()就是Protobuf官方提供的、专门解决流式通信消息边界问题的便捷方法。
内容的提问来源于stack exchange,提问作者I_got_losts_of_questions

