带有长字符串的 protobuf 中的奇怪行为
Strange behaviour in protobuf with long strings
我正在尝试将数据从客户端发送到服务器。两个应用程序都是用 java 编写的。但是他们在 SWIG Wrappers 上使用用 c++ 实现的 tls 层。 tls 层需要来自客户端的字符串,将其传输到服务器端并通知 java 服务器应用程序(并传递字符串)。但是,此字符串应包含序列化数据。不知何故,我很难使用 protobuf 来序列化数据。我想使用一个名为 ToDoListMessage
的 java protobuf class。 protobuf 看起来像这样:
message ToDoListMessage{
optional string user = 1;
optional string token = 2;
}
但是生成的javaclass无法解析之前序列化的数据:
com.google.protobuf.InvalidProtocolBufferException: Protocol message
tag had invalid wire type.
我目前没有向服务器发送数据。仅在客户端测试序列化和解析部分:
ToDoListMessageProto msg = ToDoListMessageProto.newBuilder().setUser("test").setToken("38632735722755").build();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
System.out.println("send message = " + sMsg);
ToDoListMessageProto outputmessage;
outputmessage = ToDoListMessageProto.parseFrom(sMsg.getBytes());
消息看起来像:
[10, 4, 116, 101, 115, 116, 18, 14, 51, 56, 54, 51, 50, 55, 51, 53, 55, 50, 50, 55, 53, 53]
我试过的:
1) 到目前为止我找到的所有解决方案都说这个问题可以通过使用 CodedOutputStream
来解决。但是 tls 层需要一个字符串,而不是一个流。但是我也尝试以下:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CodedOutputStream cos = CodedOutputStream.newInstance(bos);
msg.writeTo(cos);
cos.flush();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
但是对于这个解析我得到了和上面一样的错误:
CodedInputStream cis = CodedInputStream.newInstance(sMsg.getBytes());
ToDoListMessageProto message = ToDoListMessageProto.parseFrom(cis);
2) 我还尝试使用 UTF8 编码的字符串而不是类似数组的字符串:
String sMsg = new String(b);
在这种情况下,应用程序的行为更加奇怪。对于短 "tokens"(例如小于 129 位)解析有效,但对于长标记失败:
com.google.protobuf.InvalidProtocolBufferException: 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.
我真的说不出为什么。目前令牌只包含数字。
有谁知道如何从 protobuf 中获取可以正确解析的序列化字符串的解决方案?
再次声明:本次测试不涉及tls传输。目前一切都在客户端完成。
更新:
因为我直接从 Protobuf 消息中获取字节数组,所以无法传递编码。我发现消息还有一个 toByteString
方法,但在此 ByteString 上使用 toStringUtf8
似乎也不起作用:
String sMsg = msg.toByteString().toStringUtf8();
System.out.println("send message = " + sMsg);
ToDoListMessageProto outputmessage;
outputmessage = ToDoListMessageProto.parseFrom(sMsg.getBytes());
我收到相同的错误消息(如果我使用长令牌或短令牌则有所不同,请参见上文)
将 java 字符串转换为字节数组并返回,始终需要指示要使用的编码。如果省略此指示符,则只有 7 位字符(编码 "US-ASCII",因为 java7: StandardCharsets.US_ASCII)被正确转换。如果要序列化 UTF-8 字符串:
String inputStr = "öäü";
byte[] serialized = inputStr.getBytes( StandardCharsets.UTF_8);
System.out.println( "Number of bytes: " + serialized.length);
StringBuilder sb = new StringBuilder();
for (byte b : serialized)
{
sb.append(String.format("%02X ", b));
}
System.out.println( "Bytes: " + sb.toString());
String back = new String( serialized, StandardCharsets.UTF_8);
System.out.println( "Back: " + back);
给出输出:
Number of bytes: 6
Bytes: C3 B6 C3 A4 C3 BC
Back: öäü
我无法解决原来的问题。但我最终做的是生成 Java Protobuf 类 并将它们用于将数据转换为 byte[]
。之后我将 byte[]
传递给了 C++。在服务器端,我通过 JNI 将 byte[]
从 C++ TLS 层发送到 Java 服务器应用程序。 Java 服务器应用程序本身再次使用 Java Protobuf 类 将 byte[]
解析为一个对象。我的 Java 源代码中没有涉及 String
。这行得通,但我仍然很好奇,是否有办法解决原始问题。
您可以使用com.google.protobuf.TextFormat,例如:
ToDoListMessageProto msg = ToDoListMessageProto.newBuilder().setUser("test").setToken("38632735722755").build();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
System.out.println("send message = " + sMsg);
ToDoListMessageProto.Builder msgBuilder = ToDoListMessageProto.newBuilder();
TextFormat.getParser().merge(sMsg, msgBuilder);
ToDoListMessageProto outputmessage = msgBuilder.build();
System.out.println("received message = " + outputmessage.toString());
我正在尝试将数据从客户端发送到服务器。两个应用程序都是用 java 编写的。但是他们在 SWIG Wrappers 上使用用 c++ 实现的 tls 层。 tls 层需要来自客户端的字符串,将其传输到服务器端并通知 java 服务器应用程序(并传递字符串)。但是,此字符串应包含序列化数据。不知何故,我很难使用 protobuf 来序列化数据。我想使用一个名为 ToDoListMessage
的 java protobuf class。 protobuf 看起来像这样:
message ToDoListMessage{
optional string user = 1;
optional string token = 2;
}
但是生成的javaclass无法解析之前序列化的数据:
com.google.protobuf.InvalidProtocolBufferException: Protocol message tag had invalid wire type.
我目前没有向服务器发送数据。仅在客户端测试序列化和解析部分:
ToDoListMessageProto msg = ToDoListMessageProto.newBuilder().setUser("test").setToken("38632735722755").build();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
System.out.println("send message = " + sMsg);
ToDoListMessageProto outputmessage;
outputmessage = ToDoListMessageProto.parseFrom(sMsg.getBytes());
消息看起来像:
[10, 4, 116, 101, 115, 116, 18, 14, 51, 56, 54, 51, 50, 55, 51, 53, 55, 50, 50, 55, 53, 53]
我试过的:
1) 到目前为止我找到的所有解决方案都说这个问题可以通过使用 CodedOutputStream
来解决。但是 tls 层需要一个字符串,而不是一个流。但是我也尝试以下:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CodedOutputStream cos = CodedOutputStream.newInstance(bos);
msg.writeTo(cos);
cos.flush();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
但是对于这个解析我得到了和上面一样的错误:
CodedInputStream cis = CodedInputStream.newInstance(sMsg.getBytes());
ToDoListMessageProto message = ToDoListMessageProto.parseFrom(cis);
2) 我还尝试使用 UTF8 编码的字符串而不是类似数组的字符串:
String sMsg = new String(b);
在这种情况下,应用程序的行为更加奇怪。对于短 "tokens"(例如小于 129 位)解析有效,但对于长标记失败:
com.google.protobuf.InvalidProtocolBufferException: 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.
我真的说不出为什么。目前令牌只包含数字。
有谁知道如何从 protobuf 中获取可以正确解析的序列化字符串的解决方案?
再次声明:本次测试不涉及tls传输。目前一切都在客户端完成。
更新:
因为我直接从 Protobuf 消息中获取字节数组,所以无法传递编码。我发现消息还有一个 toByteString
方法,但在此 ByteString 上使用 toStringUtf8
似乎也不起作用:
String sMsg = msg.toByteString().toStringUtf8();
System.out.println("send message = " + sMsg);
ToDoListMessageProto outputmessage;
outputmessage = ToDoListMessageProto.parseFrom(sMsg.getBytes());
我收到相同的错误消息(如果我使用长令牌或短令牌则有所不同,请参见上文)
将 java 字符串转换为字节数组并返回,始终需要指示要使用的编码。如果省略此指示符,则只有 7 位字符(编码 "US-ASCII",因为 java7: StandardCharsets.US_ASCII)被正确转换。如果要序列化 UTF-8 字符串:
String inputStr = "öäü";
byte[] serialized = inputStr.getBytes( StandardCharsets.UTF_8);
System.out.println( "Number of bytes: " + serialized.length);
StringBuilder sb = new StringBuilder();
for (byte b : serialized)
{
sb.append(String.format("%02X ", b));
}
System.out.println( "Bytes: " + sb.toString());
String back = new String( serialized, StandardCharsets.UTF_8);
System.out.println( "Back: " + back);
给出输出:
Number of bytes: 6
Bytes: C3 B6 C3 A4 C3 BC
Back: öäü
我无法解决原来的问题。但我最终做的是生成 Java Protobuf 类 并将它们用于将数据转换为 byte[]
。之后我将 byte[]
传递给了 C++。在服务器端,我通过 JNI 将 byte[]
从 C++ TLS 层发送到 Java 服务器应用程序。 Java 服务器应用程序本身再次使用 Java Protobuf 类 将 byte[]
解析为一个对象。我的 Java 源代码中没有涉及 String
。这行得通,但我仍然很好奇,是否有办法解决原始问题。
您可以使用com.google.protobuf.TextFormat,例如:
ToDoListMessageProto msg = ToDoListMessageProto.newBuilder().setUser("test").setToken("38632735722755").build();
byte b [] = msg.toByteArray();
String sMsg = Arrays.toString(b);
System.out.println("send message = " + sMsg);
ToDoListMessageProto.Builder msgBuilder = ToDoListMessageProto.newBuilder();
TextFormat.getParser().merge(sMsg, msgBuilder);
ToDoListMessageProto outputmessage = msgBuilder.build();
System.out.println("received message = " + outputmessage.toString());