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;
}
}
}
我正在使用 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;
}
}
}