[英]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的相關性。
如果你還在閱讀這一點,我贊賞你的奉獻精神。
一些重要的觀察:
如上所述,這讓我發瘋! 我總是能夠解決我的編程問題,但這個問題讓我很難過。 所以我在這里請求就此主題提出任何建議或知識。
謝謝。
我懷疑你假設整個消息是在一次調用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.