BinaryFormatter 发送 TCP 消息 - 确定消息结束

BinaryFormatter to send TCP message - Determine end of message

我正在使用 BinaryFormatter.Serialize 方法发送 TCP messages.The class 我正在序列化的形式为:

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

我知道,一般来说,判断任何消息的结束有以下三种方式:

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

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

}