我使用BinaryFormatter.Serialize方法发送TCP消息。我正在序列化的类具有以下形式:
[Serializable]
public class Message {
public int senderId;
public int metaData;
public foo moreMetaData;
public object[] message;
}
我知道,一般来说,有三种方法可以确定任何消息的结束:
第三种选择似乎是个糟糕的主意。如果我使用第二个选项,我如何将一个字节附加到流并仍然能够在接收端调用BinaryFormatter.deserialize?如果我使用第一个选项(抱歉向后浏览列表),我遇到与选项二相同的问题(除了预先添加),我还有一个额外的问题,即在序列化之前确定序列化的大小,这似乎是如果没有序列化两次是不可能的 - 一旦进入虚拟变量来确定大小,然后再次进入实际流缓冲区。这里通常做什么?
答案 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;
}
}
}