2017-06-12 62 views
1

我正在寫一個TCP服務器來打開一個端口,並與某些剛剛將字符串數據作爲字節數組發送的硬件通信。C#如何手動清除TCP套接字緩衝區?

環境是在Unity中,所以我使用異步回調來避免阻止程序。這似乎工作正常,連接是有效的,正確的數據遇到,我可以發送消息到硬件,但套接字緩衝區永遠不會清除。當我做Socket.EndReceive(ar)時,數據只是堆積起來並且不會清空。

異步循環是如何工作的?我不明白爲什麼這個代碼沒有完成循環並清除緩衝區。我花了相當多的時間試圖瞭解這個過程,並不知道爲什麼這個代碼不應該工作。

protected TcpListener ListenServer; 
    protected Socket SimNetSocket; 
    protected byte[] ReadBuffer = new byte[1024]; 
    protected string MessageBuffer; 

[....] [....]

public void BeginReceive() 
    { 
     SimNetSocket.BeginReceive(ReadBuffer, 0, ReadBuffer.Length, SocketFlags.None, EndReceive, null); 
    } 

    protected void EndReceive(IAsyncResult async) 
    { 
     string msg = ""; 

     try { msg = ByteArrayToString(ReadBuffer); } 
     catch (Exception e) { Debug.LogError(e); } 

     Debug.Log("RAW RECEIVE: " + msg); 
     MessageBuffer += msg; 
     ReadBuffer = new byte[1024]; 
     SimNetSocket.EndReceive(async); 
     BeginReceive(); 
    } 

MessageBuffer是其在更新循環,其中的消息被處理並無關的問題後清除了堆疊ReadBuffer複合在套接字上。

再說一次,連接是有效的(代碼未顯示),通信正在工作。我可以看到數據成功地來自雙方,但我無法控制另一端硬件在做什麼。它是否需要一些實現來接收這些調用並確認緩衝區可以清除?

我實際上看到的是發送消息的硬件,然後是另一條消息,後來又一條消息堆疊在最後一條消息上。不過,我正在通過上面的代碼處理每條消息。所以我很困惑。

+0

EndReceive的問題已經被注意到;還包括:你使用的是什麼文本編碼?如果它不是ASCII碼,那麼你會遇到更大的問題 - 看到我對其中一個答案的評論 –

+0

調用構造函數是清除緩衝區:ReadBuffer = new byte [1024]; – jdweng

回答

1

目前尚不清楚你期望會發生什麼。 Winsock API中沒有任何內容,也沒有通過該API的精簡.NET層,將「清除」您提供的任何緩衝區。所有的API都是將讀取操作的字節複製到緩衝區中,或從緩衝區複製字節以進行寫入操作。

看着你的EndReceive()回調,你似乎誤解了這方面的一些方面。在您完成讀取操作(通過調用EndReceive())之前,您正在處理內容ReadBuffer,並且您沒有做任何事情來考慮收到的實際字節數。如果沒有一個好的Minimal, Complete, and Verifiable code example下手,這是不可能確切知道你的代碼應該做的事情,但更好的實現你的方法會是這個樣子:

protected void EndReceive(IAsyncResult async) 
{ 
    try 
    { 
     int byteCount = SimNetSocket.EndReceive(async); 

     // For example (you didn't share ByteArrayToString(), so it's not clear 
     // what encoding you're using, or if you're even processing the bytes 
     // correctly. Feel free to modify as needed...just make sure you take 
     // into account the byteCount value! 
     string msg = Encoding.ASCII.GetString(ReadBuffer, 0, byteCount); 

     Debug.Log("RAW RECEIVE: " + msg); 
     MessageBuffer += msg; 

     // There is no need to allocate a new buffer. Just reuse the one you had 
     BeginReceive(); 
    } 
    catch (IOException e) 
    { 
     // Don't catch all exceptions. Only exceptions that should be expected 
     // here would be IOException. Other unexpected exceptions should be left 
     // unhandled 
     Debug.LogError(e); 
     // You should close the socket here. Don't try to use that connection again 
    } 

} 

請注意,您可以事實上手柄除了ASCII以外的編碼,而不必擔心部分讀取。要做到這一點,你必須跟蹤從一次讀取操作到下一次讀取操作的字符解碼狀態。最簡單的方法是使用一個Decoder對象,該對象具有內部緩衝區,可以保留部分字符,直到執行下一個解碼操作。

+0

感謝您的回覆!我接受了這個答案,因爲我覺得它指出真正的問題是我誤解了這些電話會如何工作 - 這就是爲什麼我首先提出這個問題的原因。其他答覆有一些非常好的參考資料供閱讀。 – Lanefox

2

僅僅因爲您要求的ReadBuffer.Length字節被讀取並不意味着實際上有多少字節被填充到緩衝區中。您需要保留從EndReceive返回的int,並從緩衝區中只讀取該字節數。

public void BeginReceive() 
{ 
    SimNetSocket.BeginReceive(ReadBuffer, 0, ReadBuffer.Length, SocketFlags.None, EndReceive, null); 
} 

protected void EndReceive(IAsyncResult async) 
{ 
    string msg = ""; 

    int bytesRead = SimNetSocket.EndReceive(async); 
    try { msg = ByteArrayToString(ReadBuffer,bytesRead); } 
    catch (Exception e) { Debug.LogError(e); } 

    Debug.Log("RAW RECEIVE: " + msg); 
    MessageBuffer += msg; 
    //ReadBuffer = new byte[1024]; //Not necessary, you can re-use the old buffer. 
    BeginReceive(); 
} 
+1

打敗我吧。作爲OP的一個附註:還要注意,你**不能**認爲你得到*整個字符*,除非你在說ASCII。在UTF8,UTF16等的情況下 - 您可以將單個字符拆分爲多個讀取,因此您將有兩組損壞的數據 - 第一次讀取結束時,第二次讀取結​​束時一次。基本上,直到**之後,您才能將字符串轉換爲**您已完成「取景」 –

+0

@MarcGravell是的,對於OP來說,這裏是一篇非常不錯的文章,詳細瞭解構建https:/ /blog.stephencleary.com/2009/04/message-framing.html –

+0

我也願意:http://blog.marcgravell.com/2013/02/how-many-ways-can-you-mess-up -i.html –

2

您完全忽略EndReceive結果,它會告訴您接收了多少個字節。

更改EndReceive這樣的:

protected void EndReceive(IAsyncResult async) 
{ 
    string msg = ""; 

    try 
    { 
     int received = SimNetSocket.EndReceive(async); 
     var tmpArr = new byte[received]; 
     Buffer.BlockCopy(ReadBuffer, 0, tmpArr, 0, received); 
     msg = ByteArrayToString(tmpArr); 
     Debug.Log("RAW RECEIVE: " + msg); 
     MessageBuffer += msg; 
     BeginReceive(); 
    } 
    catch (Exception e) { Debug.LogError(e); } 
} 

有一些優化做的,但我不能給他們寫,因爲我沒有全碼:

- 修改ByteArrayToString避免臨時數組的創建。

- 如果在執行SimNetSocket.EndReceive(async)時拋出異常,則意味着連接已關閉,處理該情況將是一個不錯的主意。

-請注意,您將MessageBuffer上接收到的數據連接起來,這是您在使用數據時清空此變量的責任。

- 你並不認爲閱讀碎片命令的可能性(至少不在你提供的代碼中)。