通过套接字发送数据时计算字节长度有问题
本文关键字:字节 计算 有问题 数据 套接字 | 更新日期: 2023-09-27 18:03:55
我正在编写一个客户机/服务器应用程序。当使用环回地址连接时,它似乎工作得很好,但当通过局域网或互联网连接时,我遇到了一个非常奇怪的问题,我只是无法弄清楚。
在服务器和客户端之间发送的所有数据都包含在一个名为'DataPacket'的类中。然后将该类序列化为二进制,以便可以使用套接字发送。
在发送数据包之前,附加一个4字节的头,其中包含数据包的字节长度,以便接收方知道在可以反序列化之前需要多少字节。问题是,在某些时候,接收方认为它等待的数据比实际发送的数据要多得多。有时这个数字以百万字节为单位,这会引起严重的问题。
数据是这样发送的:
public override void Send(DataPacket packet)
{
byte[] content = packet.Serialize();
byte[] header = BitConverter.GetBytes(content.Length);
List<Byte> bytes = new List<Byte>();
bytes.AddRange(header);
bytes.AddRange(content);
if (socket.Connected)
{
socket.BeginSend(bytes.ToArray(), 0, bytes.Count, SocketFlags.None, OnSendPacket, socket);
}
}
在服务器端,为每个客户端分配一个StateObject——这是一个保存用户套接字、缓冲区和到目前为止已接收到的任何不完整数据的类。
这是StateObject类的样子:
public class StateObject
{
public const int HeaderSize = 4;
public const int BufferSize = 8192;
public Socket Socket { get; private set; }
public List<Byte> Data { get; set; }
public byte[] Header { get; set; }
public byte[] Buffer { get; set; }
public StateObject(Socket sock)
{
Socket = sock;
Data = new List<Byte>();
Header = new byte[HeaderSize];
Buffer = new byte[BufferSize];
}
}
StateObject类是这样被使用的:
private void OnClientConnect(IAsyncResult result)
{
Socket serverSock = (Socket)result.AsyncState;
Socket clientSock = serverSock.EndAccept(result);
StateObject so = new StateObject(clientSock);
so.Socket.BeginReceive(so.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnReceiveData, so);
}
当从客户端接收到一些数据时,调用EndReceive。如果StateObject。如果数据为空,则假定这是新数据包的第一部分,因此检查报头。如果接收到的字节数小于头中显示的大小,那么我再次调用BeginReceive,重复使用相同的StateObject。
下面是OnReceiveData的代码:private void OnReceiveData(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
int bytes = state.Socket.EndReceive(result);
if (bytes > 0)
{
state.ProcessReceivedData(bytes);
if (state.CheckDataOutstanding())
{
//Wait until all data has been received.
WaitForData(state);
}
else
{
ThreadPool.QueueUserWorkItem(OnPacketReceived, state);
//Continue receiving data from client.
WaitForData(new StateObject(state.Socket));
}
}
}
如上所示,首先我调用state.ProcessReceivedData()——这是我从数据中分离头部的地方,然后从缓冲区中移动数据:
public void ProcessReceivedData(int byteCount)
{
int offset = 0;
if (Data.Count == 0)
{
//This is the first part of the message so get the header.
System.Buffer.BlockCopy(Buffer, 0, Header, 0, HeaderSize);
offset = HeaderSize;
}
byte[] temp = new byte[byteCount - offset];
System.Buffer.BlockCopy(Buffer, offset, temp, 0, temp.Length);
Data.AddRange(temp);
}
然后调用CheckDataOutstanding()来比较接收到的字节数与报头中的大小。如果这些匹配,那么我可以安全地反序列化数据:
public bool CheckDataOutstanding()
{
int totalBytes = BitConverter.ToInt32(Header, 0);
int receivedBytes = Data.Count;
int outstanding = totalBytes - receivedBytes;
return outstanding > 0;
}
问题是,不知何故,报头中的值与发送时的值不同。这似乎是随机发生的,有时在服务器上,有时在客户端上。由于随机性和调试服务器和客户端在网络上的普遍困难,我发现几乎不可能弄清楚这个问题。
我在这里错过了什么明显或愚蠢的东西吗?
如果你只得到标题的部分会发生什么?也就是说,假设您在第一次读取中得到三个字节,而不是四个或更多字节?您的ProcessReceivedData
函数将需要考虑到这一点,并且不要尝试提取标题大小,直到您至少有四个字节。