C# 在读取内容之前从套接字接收整个数据

C# 在读取内容之前从套接字接收整个数据,c#,.net,sockets,networking,C#,.net,Sockets,Networking,我目前正在尝试设置一个服务器,它可以接受多个客户端,并可以接收和响应消息 客户机和服务器在核心使用一个公共库,其中包含一个请求类,该类被序列化并从客户机发送到服务器,反之亦然 服务器异步侦听每个套接字上的客户端,并尝试获取接收到的数据,并将数据反序列化到请求类中 数据通过网络流发送,使用二进制格式化程序直接在套接字上发送。然后在另一端使用网络流解析接收到的数据 我曾尝试使用内存流将数据存储到缓冲区,然后按如下所示对其进行反序列化,但这不起作用。直接反序列化网络流也不起作用 四处搜索,我还没有找到

我目前正在尝试设置一个服务器,它可以接受多个客户端,并可以接收和响应消息

客户机和服务器在核心使用一个公共库,其中包含一个
请求
类,该类被序列化并从客户机发送到服务器,反之亦然

服务器异步侦听每个套接字上的客户端,并尝试获取接收到的数据,并将数据反序列化到
请求
类中

数据通过
网络流
发送,使用
二进制格式化程序
直接在套接字上发送。然后在另一端使用
网络流
解析接收到的数据

我曾尝试使用
内存流
将数据存储到缓冲区,然后按如下所示对其进行反序列化,但这不起作用。直接反序列化
网络流
也不起作用

四处搜索,我还没有找到多少适用于我的用例的信息

这是套接字成功连接后的激活代码:

在请求类上,从客户端发送:

public void SendData(套接字)
{
IFormatter formatter=新的BinaryFormatter();
流=新的网络流(套接字,false);
序列化(流,this);
stream.Close();
}
接收此数据的服务器代码:

公共无效接收(套接字)
{
尝试
{
ReceiveState=新的ReceiveState(套接字);
state.Stream.BeginRead(state.Buffer,0,ReceiveState.Buffer_SIZE,新异步回调(DataReceived),state);
}
捕获(例外e)
{
Logger.LogError(例如ToString());
}
}
接收到私有无效数据(IAsyncResult ar)
{
ReceiveState=(ReceiveState)ar.AsyncState;
int bytesRead=state.Stream.EndRead(ar);
//解析消息
尝试
{
IFormatter formatter=新的BinaryFormatter();
MemoryStream MemoryStream=新的MemoryStream(state.Buffer,0,字节读取);
请求=(请求)格式化程序。反序列化(memoryStream);
Logger.Log(“成功接收请求”);
ResolveRequest(请求,state.Socket);
}
捕获(例外e)
{
Logger.LogError(例如ToString());
}
//继续听
接收(state.Socket);
}
公共类接收状态
{
公共字节[]缓冲区;
public const int BUFFER_SIZE=1024;
公共插座;
公共网络流;
公共接收状态(套接字)
{
缓冲区=新字节[缓冲区大小];
插座=插座;
流=新的网络流(套接字,false);
}
}
当前,当在
网络流上调用
BeginRead()
时,我会得到一个字节的数据,然后在调用下一个
BeginRead()
时会得到剩余的数据

e、 g.序列化数据应为:
00-01-00-00-00-FF-FF-FF-FF-01-…

我收到:
00
后接无法反序列化的
01-00-00-FF-FF-FF-FF-01-…

我认为问题在于,只要出现任何数据(即获取的单个字节),就会调用
DataReceived()
方法,然后在恢复侦听之前,剩余的数据就会到达

是否有办法确保在反序列化之前完整接收每条消息?我希望能够在收到最后一个字节后立即对对象进行反序列化。

TCP是一种流协议,而不是数据包协议。这意味着您只能保证以相同的顺序获得相同的字节(或网络故障);不能保证让它们处于相同的块配置中。所以:您需要实现自己的帧协议。框架是划分消息的方式。对于二进制消息,一个简单的帧协议可能是“length=4字节little endian int32,后跟有效负载的{length}字节”,在这种情况下,正确的解码是缓冲直到有4个字节,解码长度,缓冲{length}字节,然后解码有效负载您需要编写缓冲正确数量的代码,并且在每一点上都需要处理过度读取、反向缓冲等问题。这是一个复杂的主题。坦率地说,许多细微差别都是通过使用“管道”API解决的(我对该API进行了多部分讨论)

但是,补充指导意见:

  • 永远不要使用
    BinaryFormatter
    ,尤其是在这种情况下;它会伤害你,而且它不适合大多数用例(它也不是一个特别好的序列化程序);我的建议类似于protobuf(也许是protobuf-net),但我可以说是有偏见的
  • 网络代码微妙而复杂,RPC在很大程度上是一个“已解决”的问题;考虑尝试像GRPC这样的工具,而不是自己滚动;<李>