损坏的协议缓冲区消息
Corrupt Protocol Buffers Messages
我正在使用 Protocol Buffers for Swift(来自 CocoaPods 的最新版本)和 Google 的官方 Java Protocol buffer 客户端(版本 2.6.0)在 [=31] 之间传递消息=] 服务器 (ServerSocket) 和 Swift iOS 应用程序 (GCDAsyncSocket)。
大多数消息(每秒数百条消息;我正在将音频流式传输为浮点数组等)都运行良好。然而,有时,从客户端到服务器的消息不会被解析。 Java 代码抛出
com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero)
在两端,我发送一个 4 字节的 Big-Endian 整数,表示要跟随的字节数,然后是原始 protobuf 消息。在两端,我都收到要遵循的字节数,阻塞直到我得到那么多字节,然后尝试解析。
Java->Swift方向没有观察到错误,只有Swift->Java.
绝大多数消息都没有问题。随着正在处理的邮件数量的增加,该问题的频率似乎会增加。
在 Java 中,每个客户端都有一个与之对话的线程和一个监听它的线程。侦听器线程从线路中拉出消息并将它们放入每个客户端的 LinkedBlockingQueues 中。对话线程从该客户端的 LinkedBlockingQueue 中提取消息,将它们序列化,然后将它们发送到该客户端的输出流。
// Take a messageBuilder, serialize and transmit it
func transmit(messageBuilder: Message_.Builder) {
do {
messageBuilder.src = self.networkID;
let data = try messageBuilder.build().data()
var dataLength = CFSwapInt32HostToBig(UInt32(data.length))
self.socket.writeData(NSData(bytes: &dataLength, length: 4), withTimeout: 1, tag: 0)
self.socket.writeData(data, withTimeout: 1, tag: 0)
} catch let error as NSError {
NSLog("Failed to transmit.")
NSLog(error.localizedDescription)
}
}
Java接收方:
public void run() {
while (true) {
try {
byte[] lengthField = new byte[4];
try {
ghost.in.readFully(lengthField, 0, 4);
} catch (EOFException e) {
e.printStackTrace();
ghost.shutdown();
return;
}
Integer bytesToRead = ByteBuffer.wrap(lengthField).order(ByteOrder.BIG_ENDIAN).getInt();
byte[] wireMessage = new byte[bytesToRead];
in.readFully(wireMessage, 0, bytesToRead);
HauntMessaging.Message message = HauntMessaging.Message.parseFrom(wireMessage);
// do something with the message
} catch (IOException e) {
e.printStackTrace();
ghost.shutdown();
return;
}
}
}
有什么想法吗?
调试协议缓冲区消息:
在Wireshark中抓包
右键单击仅包含 protobuf 消息的数据包部分并复制十六进制流
使用十六进制编辑器将十六进制流保存到文件
protoc ‒‒decode_raw < file
并将输出标签和数据与 .proto 文件中的标签匹配
由于异常消息Protocol message contained an invalid tag (zero)
,我怀疑Swift未能构建protobuf消息并发送了一条空消息。
知道了!
对socket.writeData的两次连续调用不一定是原子的,而是从多个线程调用的。他们交织在一起,所以它首先写了一个长度,然后写了一个不同的长度(and/or 别人的消息)。
围绕 dispatch_async 块中的这两个调用 DISPATCH_QUEUE_SERIAL 解决了问题。
我正在使用 Protocol Buffers for Swift(来自 CocoaPods 的最新版本)和 Google 的官方 Java Protocol buffer 客户端(版本 2.6.0)在 [=31] 之间传递消息=] 服务器 (ServerSocket) 和 Swift iOS 应用程序 (GCDAsyncSocket)。
大多数消息(每秒数百条消息;我正在将音频流式传输为浮点数组等)都运行良好。然而,有时,从客户端到服务器的消息不会被解析。 Java 代码抛出
com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero)
在两端,我发送一个 4 字节的 Big-Endian 整数,表示要跟随的字节数,然后是原始 protobuf 消息。在两端,我都收到要遵循的字节数,阻塞直到我得到那么多字节,然后尝试解析。
Java->Swift方向没有观察到错误,只有Swift->Java.
绝大多数消息都没有问题。随着正在处理的邮件数量的增加,该问题的频率似乎会增加。
在 Java 中,每个客户端都有一个与之对话的线程和一个监听它的线程。侦听器线程从线路中拉出消息并将它们放入每个客户端的 LinkedBlockingQueues 中。对话线程从该客户端的 LinkedBlockingQueue 中提取消息,将它们序列化,然后将它们发送到该客户端的输出流。
// Take a messageBuilder, serialize and transmit it
func transmit(messageBuilder: Message_.Builder) {
do {
messageBuilder.src = self.networkID;
let data = try messageBuilder.build().data()
var dataLength = CFSwapInt32HostToBig(UInt32(data.length))
self.socket.writeData(NSData(bytes: &dataLength, length: 4), withTimeout: 1, tag: 0)
self.socket.writeData(data, withTimeout: 1, tag: 0)
} catch let error as NSError {
NSLog("Failed to transmit.")
NSLog(error.localizedDescription)
}
}
Java接收方:
public void run() {
while (true) {
try {
byte[] lengthField = new byte[4];
try {
ghost.in.readFully(lengthField, 0, 4);
} catch (EOFException e) {
e.printStackTrace();
ghost.shutdown();
return;
}
Integer bytesToRead = ByteBuffer.wrap(lengthField).order(ByteOrder.BIG_ENDIAN).getInt();
byte[] wireMessage = new byte[bytesToRead];
in.readFully(wireMessage, 0, bytesToRead);
HauntMessaging.Message message = HauntMessaging.Message.parseFrom(wireMessage);
// do something with the message
} catch (IOException e) {
e.printStackTrace();
ghost.shutdown();
return;
}
}
}
有什么想法吗?
调试协议缓冲区消息:
在Wireshark中抓包
右键单击仅包含 protobuf 消息的数据包部分并复制十六进制流
使用十六进制编辑器将十六进制流保存到文件
protoc ‒‒decode_raw < file
并将输出标签和数据与 .proto 文件中的标签匹配
由于异常消息Protocol message contained an invalid tag (zero)
,我怀疑Swift未能构建protobuf消息并发送了一条空消息。
知道了!
对socket.writeData的两次连续调用不一定是原子的,而是从多个线程调用的。他们交织在一起,所以它首先写了一个长度,然后写了一个不同的长度(and/or 别人的消息)。
围绕 dispatch_async 块中的这两个调用 DISPATCH_QUEUE_SERIAL 解决了问题。