使用协议缓冲区将二进制文件从 Java 服务器发送到 C# Unity3d 客户端

Posted

技术标签:

【中文标题】使用协议缓冲区将二进制文件从 Java 服务器发送到 C# Unity3d 客户端【英文标题】:Send binary file from Java Server to C# Unity3d Client with Protocol Buffer 【发布时间】:2015-09-27 16:26:32 【问题描述】:

我已经问过这个问题https://***.com/questions/32735189/sending-files-from-java-server-to-unity3d-c-sharp-client,但我发现通过内置操作在 Java 和 C# 之间发送文件并不是最佳解决方案,因为我还需要其他消息,而不仅仅是文件内容。

因此,我尝试使用 Protobuf,因为它速度快并且可以独立于平台对对象进行序列化/反序列化。我的 .proto 文件如下:

message File
  optional int32 fileSize = 1;
  optional string fileName = 2;
  optional bytes fileContent = 3;

所以,我在生成的 .java 文件中为每个变量设置了值:

file.setFileSize(fileSize);
file.setFileName(fileName);
file.setFileContent(ByteString.copyFrom(fileContent, 0, fileContent.length);

我看过很多关于如何将对象写入文件并从中读取的教程。但是,我找不到任何关于如何将文件从服务器套接字发送到客户端套接字的示例。

我的目的是在 java 服务器上序列化对象(文件大小、文件名和文件内容)并将这些信息发送到 C# 客户端。因此,该文件可以被反序列化并存储在客户端。

在我上面的示例代码中,服务器读取文件(图像文件)的字节并将其写入输出流,以便客户端可以通过输入流读取字节并将其写入磁盘。我想通过序列化生成的 .proto 文件来实现同样的目的。

谁能给我一个例子或提示我如何做到这一点?

【问题讨论】:

【参考方案1】:

如documentation 中所述,protobuf 不会处理消息的开始和停止位置,因此当使用 TCP 等流套接字时,您必须自己处理。

来自文档:

[...] 如果您想将多条消息写入单个文件或流,则由您来跟踪一条消息的结束位置和下一条消息的开始位置。协议缓冲区有线格式不是自定界的,因此协议缓冲区解析器无法自行确定消息的结束位置。解决此问题的最简单方法是在编写消息本身之前写入每条消息的大小。当您读回消息时,您读取大小,然后将字节读入单独的缓冲区,然后从该缓冲区解析。 [...]

长度前缀是一个很好的选择。根据您编写的语言,有些库可以为例如长度添加前缀。可以使用的 TCP,也可以自己定义。

线路上缓冲区的示例表示可能是以下格式(从缓冲区开始到左侧):

[buf_length|serialized_buffer2]

所以你在发送之前打包缓冲区的代码可能看起来像(这是在带有 node.js 的 javascript 中):

function pack(message) 
  var packet = new Buffer(message.length + 2);

  packet.writeIntBE(message.length, 0, 2);
  message.copy(packet, 2);

  return packet;

要阅读,你必须做相反的事情:

client.on('data', function (data) 
  dataBuffer = Buffer.concat([dataBuffer, data]);

  var dataLen = dataBuffer.readIntBE(0, 2);

  while(dataBuffer.length >= dataLen) 
    // Message length excluding length prefix of 2 bytes
    var msgLen = dataBuffer.readIntBE(0, 2);

    var thisMsg = new Buffer(dataBuffer.slice(2, msgLen + 2));

    //do something with the msg here

    // Remove processed message from buffer
    dataBuffer = dataBuffer.slice(msgLen + 2);
  
);

您还应该注意,在 TCP 套接字上发送多个 protobuf 时,它们可能会被缓冲以进行网络优化(连接)并一起发送。这意味着无论如何都需要某种分隔符。

【讨论】:

请注意,读取代码上的索引可能有点偏离,因为在我的示例中,我在标题中确实有一些额外的字段。 非常感谢您的回答!我为 Java 找到了两个函数。如果我理解正确,它的作用与您描述的相同。发送消息:writeDelimitedTo(OutputStream)。阅读消息:parseDelimitedFrom(InputStream)。你觉得用这两个函数有意义吗? @Tenshi,是的,这些功能正是这样做的 :) 如果我的回答对您有所帮助,请将其标记为已接受。

以上是关于使用协议缓冲区将二进制文件从 Java 服务器发送到 C# Unity3d 客户端的主要内容,如果未能解决你的问题,请参考以下文章

如何通过Websocket将arraybuffer作为二进制发送?

Java Web实现使用浏览器从服务器下载文件(后台)

协议缓冲区 - 将具有相同 .proto 文件的二进制数据文件合并到一个文件中

TCP/IP协议 怎么用JAVA发送和接收二进制数据 要具体实例

在 C/C++ 中处理十六进制值

java中UDP文件传输怎么实现?