2011-07-30 54 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

相關問題