Protobuf出现InvalidProtocolBufferException异常带有一些字符串。

3
我们使用protobuf v.3通过HTTP将消息从C#客户端传输到Java服务器。 消息定义如下:
message CLIENT_MESSAGE {
    string message = 1;
}

客户端和服务器都使用UTF-8字符编码来处理字符串。

当我们使用像“abc”这样的短字符串时,一切都很好,但是当我们尝试传输包含198个字符的字符串时,就会捕获到异常:

   com.google.protobuf.InvalidProtocolBufferException: 
    While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length.

我们试图比较包含protobuf数据的字节数组,但没有找到解决方案。对于字符串“aaa”,字节数组以以下字节开头:
10 3 97 97 97
其中10是protobuf字段号,3是字符串长度,69 65 67表示“aaa”。
对于包含198个字符的字符串:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
其字节数组以以下字节开头:
10 198 1 97 97 97....

其中,10代表protobuf字段编号,198代表字符串长度,1似乎是字符串标识符,对吗?

为什么protobuf无法解析此消息呢?

已经花了将近一天的时间寻找这个问题的解决方案,非常感谢任何帮助。

更新:

我们从客户端和服务器都进行了转储,但奇怪的是 - 转储是不同的!

发送到服务器之前,客户端的protobuf转储:

00000000   0A C6 01 61 61 61 61 61  61 61 61 61 61 61 61 61   ·Æ·aaaaaaaaaaaaa
00000010   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000020   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000030   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000040   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000050   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000060   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000070   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000080   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00000090   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
000000A0   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
000000B0   61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
000000C0   61 61 61 61 61 61 61 61  61                        aaaaaaaaa  

服务器接收到的Protobuf转储:

0000: 0A EF BF BD 01 61 61 61 61 61 61 61 61 61 61 61   .....aaaaaaaaaaa
0010: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0020: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0030: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0040: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0050: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0060: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0070: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0080: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
0090: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00A0: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00B0: 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
00C0: 61 61 61 61 61 61 61 61 61 61 61                   aaaaaaaaaaa

正如您所看到的,protobuf数据头是不同的...这完全让我困惑,这怎么可能发生?

更新2:我们进行了研究,并发现这个问题只发生在长度大于128个字符的字符串中。如果字符串由128个或更少的字符组成,则没有问题。


更新:我们进行了研究,发现此问题仅在字符串长度超过128个字符时发生。如果字符串由128个字符或更少组成,则会出现此问题。 - NewJ
2个回答

6

最终问题出在字符编码上 - 我们尝试将二进制protobuf数据转换为字符串。

如果您需要将二进制protobuf数据作为字符串传输 - 请先在客户端对其进行base64编码,然后在服务器上解码base64。

感谢@Marc Gravell提供的帮助。


2
请注意:如果您可以避免处理字符串,那么会更有效率 - base-64 会增加一些开销。 - Marc Gravell

2

10是protobuf字段编号,

是的;字段1,长度前缀。

198是字符串长度,1似乎像是字符串标识符,或者是什么?

198 1 是使用“varint”编码的字符串长度;这相当于整数198,但需要两个字节来编码。

为什么protobuf无法解析此消息?

我们需要看到其余的字节;如果您没有所有字节,则库可能非常正确。您是否拥有失败案例的所有字节,例如十六进制或base-64?


@NewJ 这里的总缓冲区应该是201字节,请注意:如果您提供的缓冲区长度不是这个长度,那么就会出现问题。 - Marc Gravell
@NewJ 啊,我没有看到带有 pastebin 数据的编辑;是的,那个数据看起来正确,并且对我来说解码正确。所以,下一个问题是:服务器正在处理的确切数据(包括长度)是什么?它也是完全相同的 201 字节数据吗? - Marc Gravell
1
@NewJ 看一下文件的开头;客户端发送了“0A C6 01 61xlots”,服务器接收到的是“0A EF BF BD 01 61xlots”;在服务器接收到你发送的数据之前,其他任何操作都不会起作用——你破坏了传输中的数据。那么:为什么服务器会得到这个呢?我猜测你已经对二进制进行了文本编码,这是非常错误的做法。 - Marc Gravell
1
@NewJ,我扫描了我所能想到的所有文本编码方式,都无法从“0A C6 01 61”生成“0A EF BF BD 01 61”。因此...我不确定你到底做了什么,但在客户端进行序列化和在服务器上进行反序列化之间的某个地方:数据是不同的。因此:直到服务器上的数据与客户端认为自己发送的数据匹配为止:一切都是未知的。我看不到那段代码,因此我无法推测你做了什么。 - Marc Gravell
2
@NewJ 嗯,是的;那一行代码是错误的 - 它在反向使用文本编码。这不是将任意二进制(即:未编码的文本)转换为字符串的有效方法。你需要的是像十六进制或基础64这样的东西。我会选择基础64(它会更短),这样写:string base64 = Convert.ToBase64String(protoBytes);。几乎所有框架都有预先构建的API来编码或解码基础64。 - Marc Gravell
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接