【问题标题】:BinaryFormatter to send TCP message - Determine end of message [closed]BinaryFormatter 发送 TCP 消息 - 确定消息结束 [关闭]
【发布时间】:2017-04-13 16:04:03
【问题描述】:

我正在使用 BinaryFormatter.Serialize 方法发送 TCP 消息。我正在序列化的类的形式是:

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

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

  • 前置大小字节
  • 附加消息字节的结尾
  • 固定消息长度

第三个选项似乎是个糟糕的主意。如果我使用第二个选项,我怎样才能在流中附加一个字节并且仍然能够在接收端调用 BinaryFormatter.deserialize?如果我使用第一个选项(对不起,向后浏览列表),我会遇到与选项二相同的问题(除了前置),并且我还有一个额外的问题是在序列化之前确定序列化的大小,这似乎没有序列化两次是不可能的 - 一次进入一个虚拟变量以确定大小,然后再次进入真正的流缓冲区。这里一般是做什么的?

【问题讨论】:

    标签: c# serialization tcp binaryformatter


    【解决方案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;
            }
        }
    
    }
    

    【讨论】:

    • 我正在使用byte[] buf = new byte[1024]; await networkStream.ReadAsync(buf, 0, buf.Length); 从缓冲区中读取我的消息。如何确定何时调用 BinaryFormatter.deserialize?
    • @WreckFish 您只需使用 ReadAsync 阅读足够的内容来判断下一个消息类型将是什么,然后将流传递给 Desearalize 函数。我用一个例子更新了我的答案。
    • 谢谢。它变得越来越清晰。但是,我对该示例仍有一些疑问。这如何确定在var read = await networkStream.ReadAsync(buffer, 0, 1).ConfigureAwait(false); 之后何时收到其余数据。我假设这与SynchronizationContext.CurrentConfigureAwait(false) 有关,但我不明白这些东西是什么或做什么。另外,为什么这里有这么多异步调用?一旦接收到数据,我们就不能同步调用 deserialize() 和 processFoo() 吗?
    • 如果您在 UI 线程上并且您正在等待的事情在调用返回之前完成(例如,如果您从 ReadAsync 读取的数据已经在操作系统的网络缓冲区中)调用从不切换上下文,因此 ConfigureAwait(false) 对其没有影响。我提出这么多异步调用的原因是因为我想让一些函数处理数据,并且我想表明这些进程不会阻塞线程。
    • 至于“何时”处理数据,只要我们读入的第一个字节可用,它就会开始处理,之后formatter.Deserialize(networkStream)将阻塞,直到NetworkStream传输完所有数据,这就是为什么我们在同步上下文中将其放在Task.Run 上的原因,因此我们不会阻塞 UI 线程。
    猜你喜欢
    • 2015-03-14
    • 1970-01-01
    • 2012-03-13
    • 2020-09-15
    • 1970-01-01
    • 1970-01-01
    • 2016-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多