在Java中读取protobuf消息时的例外情况

时间:2011-07-02 15:19:32

标签: java c++ sockets tcp protocol-buffers

我现在正在使用 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条消息平均被破坏。

2 个答案:

答案 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解析调用都会在内部使用CodedInputStreamsrc here),如果源为InputStream,则依赖于read() - 因此,受TCP套接字问题的影响。

因此,当您通过套接字填充大量数据时,某些消息必然会分成两帧 - 这就是它们被破坏的地方。

我猜,当你降低邮件传输速率时(正如你所说的每秒6-8条消息),每个帧在下一个数据块被放入流之前被发送,因此每条消息总是得到它自己的TCP帧,即没有得到拆分,不会得到错误。 (或者可能只是错误是罕见的,低速率意味着你需要更多的时间来看待它们)

至于解决方案,最好的办法是自己处理缓冲区,即从套接字中读取byte[](可能使用readFully()代替read(),因为前者会阻止直到有足够的数据来填充缓冲区[或遇到EOF],所以它对中间消息帧结束的东西有抵抗力,确保它有足够的数据被解析成一个完整的消息,然后输入缓冲区解析器。

另外,在this Google Groups topic中对这个主题有一些很好的阅读 - 这就是我得到readFully()部分的地方。