2011-07-30 55 views
2

我正在编写客户端/服务器应用程序。当使用环回地址连接时,它似乎工作得很好,但当通过局域网或互联网连接时,我遇到了一个很奇怪的问题,我无法弄清楚。通过套接字发送数据时计算字节长度的问题

在服务器和客户端之间发送的所有数据都包含在名为“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.Data为空,则假定这是新数据包的第一部分,因此检查标题。如果接收的字节数小于头中显示的大小,那么我再次调用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; 
} 

的问题是,不知何故在标题中值在不同的值,以它作为被送往结束了。这似乎是随机发生的,有时发生在服务器上,有时发生在客户端。由于随机性和在网络上调试服务器和客户端的一般难度,我发现几乎不可能弄清楚这一点。

有什么明显的或愚蠢,我在这里失踪?

+0

代码张贴只能为* *一个数据包正常工作。重置StateObject所需的代码,特别是清空Data的代码缺失。足以导致所描述的问题。 –

回答

2

如果你的头的唯一部分会发生什么?也就是说,假设你在第一次读取时有三个字节,而不是四个或更多?你的ProcessReceivedData函数将需要考虑到这一点,而不是尝试提取标题大小,直到你至少有四个字节。

+0

虽然你是对的,这是我忽略了,而且肯定需要考虑,但我不认为这是问题的原因。我只是试着用一个断点在ProcessReceivedData中添加少于4个字节的检查,并且在问题发生时它从来没有被打过。 – Adam

相关问题