2017-04-13 58 views
-1

我使用BinaryFormatter.Serialize方法发送TCP messages.The类我序列化的形式为结束:的BinaryFormatter发送TCP消息 - 确定消息

[Serializable] 
public class Message { 
     public int senderId; 
     public int metaData; 
     public foo moreMetaData; 
     public object[] message; 
} 

我知道,在一般中,有三种方法来确定任何消息的结束:

  • 前置大小字节
  • 消息的
  • 追加端字节
  • 固定消息长度

第三个选项似乎是一个可怕的想法。如果我使用第二个选项,如何在流中添加一个字节并仍然能够在接收端调用BinaryFormatter.deserialize?如果我使用第一个选项(对于向后遍历列表感到抱歉),我有与选项2相同的问题(除了预先计划),并且在序列化之前我还有另外一个问题,即确定序列化的大小,如果不进行两次序列化 - 一次成为虚拟变量以确定大小,然后再次进入真正的流缓冲区。通常在这里做什么?

回答

1

BinaryFormatter在内部已经实现了“Prepend size byte”。您只需将您的NetworkStream对象传入BinaryFormatter.Deserialize方法,并且可以自行计算需要读取的字节数。

注意: BinaryFormatter对程序集中的版本差异非常敏感。如果一端有一个版本的程序,而另一端版本稍旧,则两端可能无法相互通话。我建议使用一个二进制序列化程序,它不会将模型绑定到程序集版本号。 ProtoBuf-net是一个很好的库来代替。

编辑:这里是你如何能做到这

private async Task MessageLoop(NetworkStream networkStream) 
{ 
    //Lets pretend our protocall sends a byte with: 
    // - 1 if the next object will be a Foo, 
    // - 2 if the next object will be a Bar 
    // - 3 if the next object will be a Int32. 

    var formatter = new BinaryFormatter(); 
    byte[] buffer = new byte[1024]; 

    while (true) 
    { 
     var read = await networkStream.ReadAsync(buffer, 0, 1).ConfigureAwait(false); 
     if (read < 0) 
     { 
      await LogStreamDisconnectAsync(); 
     } 

     switch (buffer[0]) 
     { 
      case 1: 
       //If we are on a SynchronizationContext run the deseralize function on a new thread because that call will block. 
       Func<Foo> desearalize =()=> (Foo)formatter.Deserialize(networkStream); 
       Foo foo; 
       if (SynchronizationContext.Current != null) 
       { 
        foo = await Task.Run(desearalize).ConfigureAwait(false); 
       } 
       else 
       { 
        foo = desearalize(); 
       } 

       await ProcessFooAsync(foo).ConfigureAwait(false); 
       break; 
      case 2: 
       var bar = await Task.Run(() => (Bar)formatter.Deserialize(networkStream)).ConfigureAwait(false); 
       await ProcessBarAsync(bar).ConfigureAwait(false); 
       break; 
      case 3: 

       //We have to loop on Read because we may not get 4 bytes back when we do the call, so we keep calling till we fill our buffer. 
       var bytesRead = 0; 
       while (bytesRead < 4) 
       { 
        //We don't want to overwrite the buffer[0] so we can see the value in the debugger if we want, so we do 1 + bytesRead as the offset. 
        bytesRead += await networkStream.ReadAsync(buffer, 1 + bytesRead, 4 - bytesRead).ConfigureAwait(false); 
       } 

       //This assumes both ends have the same value for BitConverter.IsLittleEndian 
       int num = BitConverter.ToInt32(buffer, 1); 

       await DoSomethingWithANumberAsync(num).ConfigureAwait(false); 

       return; 
      default: 
       await LogInvaidRequestTypeAsync(buffer[0]).ConfigureAwait(false); 
       return; 
     } 
    } 

} 
+0

我使用 '的byte [] buf中=新的字节一个例子[1024]; 等待networkStream.ReadAsync(buf,0,buf.Length);' 从缓冲区中读取我的消息。我如何确定何时调用BinaryFormatter.deserialize? – WreckFish

+0

@WreckFish你只有用ReadAsync阅读足够的内容才能知道下一个消息类型是什么,然后将流传递给Desearalize函数。我用一个例子更新了我的答案。 –

+0

谢谢。它变得越来越清晰。不过,我仍然有一些关于这个例子的问题。这是如何确定在'var read = await networkStream.ReadAsync(buffer,0,1).ConfigureAwait(false);'后收到其余数据的时候。我假设这与'SynchronizationContext.Current'和'ConfigureAwait(false)'有关,但我不明白这些东西是什么或做什么。另外,为什么在这里有这么多的异步调用?一旦收到数据,我们是否能够同时进行诸如deserialize()和processFoo()之类的调用? – WreckFish