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

时间:2017-04-13 16:04:03

标签: c# serialization tcp binaryformatter

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

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

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

  • 前置大小字节
  • 追加消息字节结尾
  • 固定邮件长度

第三种选择似乎是个糟糕的主意。如果我使用第二个选项,我如何将一个字节附加到流并仍然能够在接收端调用BinaryFormatter.deserialize?如果我使用第一个选项(抱歉向后浏览列表),我遇到与选项二相同的问题(除了预先添加),我还有一个额外的问题,即在序列化之前确定序列化的大小,这似乎是如果没有序列化两次是不可能的 - 一旦进入虚拟变量来确定大小,然后再次进入实际流缓冲区。这里通常做什么?

1 个答案:

答案 0 :(得分: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;
        }
    }

}