簡體   English   中英

非常奇怪的問題通過C#中的套接字發送數據

[英]Very Strange Problem sending data via Sockets in C#

我為這篇冗長的帖子道歉。 我仍然盡可能小,同時仍然傳達問題。

好的,這讓我發瘋了。 我有一個客戶端和一個服務器程序,都在C#中。 服務器通過Socket.Send()將數據發送到客戶端。 客戶端通過Socket.BeginReceive和Socket.Receive接收數據。 我的偽協議如下:服務器發送一個雙向(短)值,表示實際數據的長度,緊接着是實際數據。 客戶端異步讀取前兩個字節,將字節轉換為short,並立即同步從套接字讀取多個字節。

現在每隔幾秒左右就可以正常工作一個周期,但是當我提高速度時,事情會變得奇怪。 似乎客戶端在嘗試從兩個字節長度讀取時會隨機讀取實際數據。 然后它嘗試將這些任意兩個字節轉換為short,這會導致完全不正確的值,從而導致崩潰。 以下代碼來自我的程序,但修剪為僅顯示重要的行。

用於發送數據的服務器端方法:

private static object myLock = new object();
private static bool sendData(Socket sock, String prefix, byte[] data)
{
    lock(myLock){
        try
        {
            // prefix is always a 4-bytes string
            // encoder is an ASCIIEncoding object    
            byte[] prefixBytes = encoder.GetBytes(prefix);
            short length = (short)(prefixBytes.Length + data.Length);

            sock.Send(BitConverter.GetBytes(length));
            sock.Send(prefixBytes);
            sock.Send(data);

            return true;
        } 
        catch(Exception e){/*blah blah blah*/}
    }
}

用於接收數據的客戶端方法:

private static object myLock = new object();
private void receiveData(IAsyncResult result)
{
    lock(myLock){
        byte[] buffer = new byte[1024];
        Socket sock = result.AsyncState as Socket;
        try
        {
            sock.EndReceive(result);
            short n = BitConverter.ToInt16(smallBuffer, 0);
            // smallBuffer is a 2-byte array

            // Receive n bytes
            sock.Receive(buffer, n, SocketFlags.None);

            // Determine the prefix.  encoder is an ASCIIEncoding object
            String prefix = encoder.GetString(buffer, 0, 4);

            // Code to process the data goes here

            sock.BeginReceive(smallBuffer, 0, 2, SocketFlags.None, receiveData, sock);
        }
        catch(Exception e){/*blah blah blah*/}
    }
}

服務器端代碼可靠地重新創建問題:

byte[] b = new byte[1020];  // arbitrary length

for (int i = 0; i < b.Length; i++)
    b[i] = 7;  // arbitrary value of 7

while (true)
{
    sendData(socket, "PRFX", b);
    // socket is a Socket connected to a client running the same code as above
    // "PRFX" is an arbitrary 4-character string that will be sent
}

查看上面的代碼,可以確定服務器將永遠發送數字1024,包括前綴在內的總數據的長度為短(0x400),后跟ASCII二進制中的“PRFX”,后跟一堆7的(0×07)。 客戶端將永遠讀取前兩個字節(0x400),將其解釋為1024,將該值存儲為n,然后從流中讀取1024個字節。

這確實是它在前40次左右的迭代中所做的事情,但是,自發地,客戶端將讀取前兩個byes並將它們解釋為1799,而不是1024! 十六進制中的1799是0x0707,這是連續兩個7! 那是數據,而不是長度! 這兩個字節發生了什么變化? 這種情況發生在我放在字節數組中的任何值,我只選擇7,因為很容易看到與1799的相關性。

如果你還在閱讀這一點,我贊賞你的奉獻精神。

一些重要的觀察:

  • 減小b的長度將增加問題發生之前的迭代次數,但不會阻止問題的發生
  • 在循環的每次迭代之間添加顯着延遲可以防止問題發生
  • 在同一主機上同時使用客戶端和服務器並通過環回地址連接時, 不會發生這種情況。

如上所述,這讓我發瘋! 我總是能夠解決我的編程問題,但這個問題讓我很難過。 所以我在這里請求就此主題提出任何建議或知識。

謝謝。

我懷疑你假設整個消息是在一次調用receiveData傳遞的。 一般情況下,情況並非如此。 碎片,延遲等最終可能意味着數據以運行的形式到達,因此您可以在僅有900字節就緒時調用您的receiveData函數。 你的調用sock.Receive(buffer, n, SocketFlags.None); 說“將數據輸入緩沖區,最多n個字節” - 您可能會收到更少的數據,並且Receive返回實際讀取的字節數。

這解釋了為什么減少b ,增加更多延遲或使用相同的主機似乎“修復”了問題 - 使用這些方法一次性到達整個消息的可能性大大增加(較小的b ,較小的數據;添加延遲意味着管道中的總體數據較少;本地地址沒有網絡)。

要查看這確實是問題,請記錄Receive的返回值。 如果我的診斷正確,它偶爾會小於1024。 要解決這個問題,您需要等到本地網絡堆棧的緩沖區包含所有數據(不理想),或者只是在數據到達時接收數據,將其存儲在本地,直到整個消息准備就緒並進行處理。

聽起來像我這里的主要問題是沒有檢查 EndReceive的結果,它是讀取的字節數。 如果套接字打開,則可以是> 0<=您指定的最大值。 假設因為你要求2個字節,讀取2個字節是不安全的。 在閱讀實際數據時同樣如此。

您必須始終循環,累積所需的數據量(並減少剩余計數,並相應地增加偏移量)。

同步/異步的混合無助於簡化:£

sock.Receive(buffer, n, SocketFlags.None);

您沒有檢查函數的返回值。 插座將返回多達 “N”個字節,但只會返回什么是可用的。 您有可能無法通過此閱讀獲得全部“數據”。 因此,當您執行下一個套接字讀取時,實際上是獲取剩余數據的前兩個字節而不是下一個傳輸的長度。

使用套接字時,必須預期套接字可能傳輸的字節數少於預期。 您必須循環.Receive方法以獲取剩余的字節。

通過套接字發送字節時也是如此。 您必須檢查發送了多少字節,然后循環發送直到發送完所有字節。

此行為是由於網絡層將郵件拆分為多個數據包。 如果您的消息很短,那么您就不太可能遇到此消息。 但是你應該總是為它編碼。

每個緩沖區使用更多字節,您很可能會看到發送方的消息吐入多個數據包。 每次讀取操作都將收到一個數據包,這只是您郵件的一部分。 但是小緩沖區也可以分開。

我的猜測是, smallBuffer在receive方法之外聲明,並且正在從多個線程重用。 換句話說,這不是線程安全的。

編寫好的套接字代碼很難。 由於您正在編寫客戶端和服務器,因此您可能需要查看Windows Communication Foundation (WCF),它可以讓您輕松地發送和接收整個對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM