带有长串的protobuf中的奇怪行为

时间:2016-03-22 20:12:32

标签: java c++ serialization protocol-buffers

我正在尝试将数据从客户端发送到服务器。这两个应用程序都是用java编写的。但是他们使用在c ++上实现的tls层而不是SWIG Wrappers。 tls层需要来自客户端的字符串,将其传输到服务器端并通知java服务器应用程序(并传递字符串)。但是,此字符串应包含序列化数据。不知何故,我很难使用protobuf来序列化数据。我想使用名为ToDoListMessage的java protobuf类。 protobuf看起来像这样:

message ToDoListMessage{  
    optional string user = 1;  
    optional string token = 2;
}

但是生成的java类无法解析之前序列化的数据:

  

com.google.protobuf.InvalidProtocolBufferException:协议消息   标签的线路类型无效。

我目前没有将数据发送到服务器。只需测试客户端上的序列化和解析部分:

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);

在这种情况下,应用程序表现得更加奇怪。对于简短的“令牌”(例如小于129位),解析有效,但是对于长令牌失败:

  

com.google.protobuf.InvalidProtocolBufferException:解析时   协议消息,输入意外地在a中间结束   领域。这可能意味着输入被截断或者被截断   嵌入式消息误报了自己的长度。

我真的不知道为什么。目前,令牌只包含数字。

有没有人知道如何从protobuf获取可以正确解析的序列化字符串的解决方案?

再次说明:此测试中没有涉及tls传输。目前一切都在客户端完成。

更新

因为我直接从Protobuf消息中获取字节数组,所以无法传递编码。我发现该消息还有toByteString方法,但在此ByteString上使用toStringUtf8似乎不起作用:

String sMsg = msg.toByteString().toStringUtf8();
System.out.println("send message = " + sMsg);
ToDoListMessageProto outputmessage;         
outputmessage = ToDoListMessageProto.parseFrom(sMsg.getBytes());

我收到相同的错误消息(如果我使用长令牌或短令牌,则会有所不同,见上文)

3 个答案:

答案 0 :(得分:2)

将java String转换为字节数组并返回始终需要指示要使用的编码。如果省略此指示符,则只能正确转换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: öäü

答案 1 :(得分:0)

我无法解决原来的问题。但我最终做的是生成Java Protobuf类并使用它们将数据转换为byte[]。之后我将byte[]传递给C ++。在服务器端,我通过JNI将byte[]从C ++ TLS层发送到Java服务器应用程序。 Java服务器应用程序本身再次使用Java Protobuf类将byte[]解析为对象。我的Java源代码中没有String。这有效,但我仍然很好奇,如果有办法解决原始问题。

答案 2 :(得分:0)

您可以使用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());