我现在正在使用 protobuf 几周,但在解析 Java 中的protobuf消息时仍然会遇到异常。
我使用 C ++ 创建我的protobuf消息,并使用 boost sockets 将它们发送到Java客户端正在侦听的服务器套接字。用于传输消息的C ++代码是:
boost::asio::streambuf b;
std::ostream os(&b);
ZeroCopyOutputStream *raw_output = new OstreamOutputStream(&os);
CodedOutputStream *coded_output = new CodedOutputStream(raw_output);
coded_output->WriteVarint32(agentMessage.ByteSize());
agentMessage.SerializeToCodedStream(coded_output);
delete coded_output;
delete raw_output;
boost::system::error_code ignored_error;
boost::asio::async_write(socket, b.data(), boost::bind(
&MessageService::handle_write, this,
boost::asio::placeholders::error));
正如您所看到的那样,我使用WriteVarint32
编写了消息的长度,因此Java方应该知道它应该读取parseDelimitedFrom
:
AgentMessage agentMessage = AgentMessageProtos.AgentMessage
.parseDelimitedFrom(socket.getInputStream());
但是没有任何帮助,我一直得到这样的例外:
Protocol message contained an invalid tag (zero).
Message missing required fields: ...
Protocol message tag had invalid wire type.
Protocol message end-group tag did not match expected tag.
While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either than the input has been truncated or that an embedded message misreported its own length.
重要要知道,每条消息都不会抛出这些异常。这只是我收到的最多工作信息的一小部分 - 我仍然希望解决这个问题,因为我不想省略这些信息。
如果有人可以帮助我或者用他的想法,我会非常感激。
另一个有趣的事实是我收到的邮件数量。通常,对于我的程序,2秒内的总消息为1.000秒。在20秒内约为100.000等。我减少了人工发送的消息,当只发送6-8条消息时,根本没有错误。那么这可能是Java客户端套接字端的缓冲问题吗?
开,让我们说60.000条消息,其中5条消息平均被破坏。
答案 0 :(得分:1)
我不熟悉Java API,但我想知道Java如何处理表示消息长度的uint32值,因为Java只有32位整数。快速浏览Java API参考告诉我,无符号的32位值存储在带符号的32位变量中。那么如何处理无符号32位值表示消息长度的情况呢?此外,Java实现中似乎支持varint signed signed。它们被称为ZigZag32 / 64。 AFAIK,C ++版本不知道这样的编码。那么问题的原因可能与这些事情有关吗?
答案 1 :(得分:1)
[我不是真正的TCP专家,可能会离开]
问题是,[Java] TCP Socket的read(byte[] buffer)
将在读取到TCP帧结束后返回。如果恰好是消息中间(我的意思是,protobuf消息),解析器将会阻塞并抛出InvalidProtocolBufferException
。
任何protobuf解析调用都会在内部使用CodedInputStream
(src here),如果源为InputStream
,则依赖于read()
- 因此,受TCP套接字问题的影响。
因此,当您通过套接字填充大量数据时,某些消息必然会分成两帧 - 这就是它们被破坏的地方。
我猜,当你降低邮件传输速率时(正如你所说的每秒6-8条消息),每个帧在下一个数据块被放入流之前被发送,因此每条消息总是得到它自己的TCP帧,即没有得到拆分,不会得到错误。 (或者可能只是错误是罕见的,低速率意味着你需要更多的时间来看待它们)
至于解决方案,最好的办法是自己处理缓冲区,即从套接字中读取byte[]
(可能使用readFully()
代替read()
,因为前者会阻止直到有足够的数据来填充缓冲区[或遇到EOF],所以它对中间消息帧结束的东西有抵抗力,确保它有足够的数据被解析成一个完整的消息,然后输入缓冲区解析器。
另外,在this Google Groups topic中对这个主题有一些很好的阅读 - 这就是我得到readFully()
部分的地方。