Socket.Receive 方法 (Byte(), Int32, Int32, SocketFlags)
使用指定的 SocketFlags,從綁定的 Socket 接收指定的字節(jié)數(shù),存入接收緩沖區(qū)的指定偏移量位置。
public int Receive( byte[] buffer, int offset, int size, SocketFlags socketFlags )
參數(shù)
buffer
類型:System.Byte()
Byte 類型的數(shù)組,它是存儲接收到的數(shù)據(jù)的位置。
offset
類型:System.Int32
buffer 中存儲所接收數(shù)據(jù)的位置。
size
類型:System.Int32
要接收的字節(jié)數(shù)。
socketFlags
類型:System.Net.Sockets.SocketFlags
SocketFlags 值的按位組合。
返回值
類型:System.Int32
接收到的字節(jié)數(shù)。
(以上部分來自MSDN)
接下來我的經(jīng)驗總結(jié):
1,Socket實際接收到的字節(jié)數(shù)與預(yù)期想要接收字節(jié)數(shù)
我們從上面的返回值可以清晰的知道,雖然我們給Buffer定義了一個長度和要接收到的size,但是Socket接收到的字節(jié)數(shù)(即返回值)并不一定等于Size
所以我們在做這個的時候,需要判斷,實際接收到的字節(jié)數(shù)是否等于要接收的字節(jié)數(shù)!這個很重要。
//定義接收數(shù)據(jù)的緩存 byte[] body = new byte[1024]; //第一次接收的實際數(shù)據(jù) flag int flag = socket.Receive(body, 0, body.Length, SocketFlags.None); //如果沒有接收到定長的數(shù)據(jù),循環(huán)接收 while(flag != body.Length) { flag += socket.Receive(body, flag, body.Length - flag, SocketFlags.None); }
因此,Socket接收數(shù)據(jù)的過程應(yīng)該是這樣的!
2,服務(wù)器發(fā)送多次,客戶端一次接收的問題。
當(dāng)然在實際的編程中很少這么干的,但是我這么寫是想說明一個問題:假如客戶端想一次接收服務(wù)器多次發(fā)送過來的數(shù)據(jù),客戶端定義緩存body的長度等于服務(wù)端多次發(fā)送的數(shù)據(jù)長度和,客戶端在服務(wù)器發(fā)送完成后開始一次接收,客戶端肯定能接收到服務(wù)器發(fā)送過來的所有數(shù)據(jù),不會只接收服務(wù)器發(fā)送過來的部分?jǐn)?shù)據(jù)。
我的敘述很爛!打個比方,假如服務(wù)器分三次分別發(fā)送“Hell”,“0”,“World”。這樣客戶端一定能接受到“HelloWorld”。而不是“Hell”或者其他什么的東西。
最近在調(diào)試程序的時候,希望1秒鐘調(diào)用一次 Socket.Receive() 來接收數(shù)據(jù)。
實際上,應(yīng)該是說,如果沒有數(shù)據(jù)到來,就是1秒鐘一次,如果有數(shù)據(jù)到來,則收到數(shù)據(jù)后立即繼續(xù)接收,然后繼續(xù)是1秒鐘接收一次。
C#的相關(guān)代碼如下:
public bool ReadDataForMe(Socket sck, uint waitSec) { int rlen = 1; DateTime ttStart = DateTime.Now; //sck.ReceiveTimeout = 5; sck.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, -300); while (true) { try { PrintLog("start read socket.\n"); if (BaseTools.GetEclpseTime(ttStart, 0.4) >= waitSec) { break; } rlen = sck.Receive(m_bufRecv, 0, 1000, SocketFlags.None); if (rlen == 0) { BaseTools.Sleep(100); continue; } return true; } catch (IOException) { // 讀取失敗,原因:基礎(chǔ) System.Net.Sockets.Socket 被關(guān)閉。 return false; } catch (SocketException e) { if (e.ErrorCode == 10060) // 超時的時候錯誤號碼是10060 continue; break; } catch (Exception e) { break; } }return false; }// 如下調(diào)用while(true) { if (ReadDataForMe(mySock, 1)) break; }
其中BaseTools為一個基本工具庫,自己封裝的,這里用到了兩個功能,一個是Sleep(),用于休息多長時間,另外一個是 GetEclpseTime() 用于獲取指定的一個時間到當(dāng)前時間的秒數(shù),這樣用于計算時間差。
另外還有一個函數(shù) PrintLog() 用于在控制臺打印字符串,其中添加了時間信息,代碼如下:
public static void PrintLog(string s) { Console.WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] {s}"); }
在每一次打印都添加了時間,精確到毫秒,這樣方便對照打印時的時間。
測試方法就是連上服務(wù)器之后,服務(wù)器不發(fā)送數(shù)據(jù),此客戶端一直調(diào)用Receive()接口來收取數(shù)據(jù)。在每一次接收數(shù)據(jù)之前都加一行打?。簊tart read socket.
以上為測試的環(huán)境情況。
下面測試主要是對函數(shù)開始的前幾行的socket超時設(shè)置以及對超時時間的觀察。
如上面的代碼那樣,設(shè)置超時時間為 -300,則打印間隔為500ms
如果改成3,則打印間隔為503ms
前幾日碰到一問題,當(dāng)CSocket的Receive阻塞時,如何進行超時處理。由于程序是在多線程中使用Socket通信,開始時是在主線程中用定時監(jiān)測Receive函數(shù),當(dāng)超時后,結(jié)束通信。但問題是CSocket對象無法釋放。因此從網(wǎng)上搜索解決辦法,直接在線程中對Receive進行超時處理。
不錯,搜到以下內(nèi)容,很多網(wǎng)站轉(zhuǎn)載。
為CSocket配置Time-Out功能
CSocket操作,如Send(),Receive(),Connect()都屬阻塞操作,即它們在成功完成或錯誤發(fā)生之前是不會返回的。
在某些情況下,某項操作可能永遠(yuǎn)不能成功完成,程序為了等待其完成就得永遠(yuǎn)循環(huán)下去。在程序中為某項操作限定一個成功完成的時間是個好主意。本文就是討論此問題的。
一個辦法是設(shè)計一個計時器,當(dāng)操作費時過長時就觸發(fā)。這個辦法的關(guān)鍵是怎樣處理計時器。雖然操作是"阻塞"的,但仍具處理傳回的消息的能力。如果用SetTimer來設(shè)置計時器,就可截獲WM_TIMER消息,當(dāng)它產(chǎn)生時就終止操作。涉及到這個過程的主要函數(shù)是:Windows API ::SetTimer(),MFC函數(shù)CSocket::OnMessagePending()和CSocket:: CancelBlockingCall()。這些功能可包裝到你的CSocket類中得以簡化。
類中用到三個重要函數(shù):
BOOL SetTimeOut(UINT uTimeOut) 它應(yīng)在CSocket函數(shù)調(diào)用前被調(diào)用。uTimeOut以千分秒為單位。下面的實現(xiàn)只是簡單的設(shè)置計時器。當(dāng)設(shè)置計時器失敗時返回False。參見Windows API中關(guān)于SetTimer的說明。
BOOL KillTimeOut() 此函數(shù)應(yīng)在操作未完成被阻塞時被調(diào)用。它刪除SetTimeOut所設(shè)置的計時器。如果調(diào)用KillTimer失敗則返回False。參見Windows API中關(guān)于KillTimer的說明。
BOOL OnMessagePending() 它是一個虛擬回調(diào)函數(shù),當(dāng)?shù)却僮魍瓿蓵r被CSocket類調(diào)用。它給你機會來處理傳回的消息。這次我們用它來檢查SetTimeOut所設(shè)置的計時器,如果超時(Time-Out),則它調(diào)用CancelBlockingCall()。參見MFC文檔關(guān)于OnMessagePending()和CancelBlockingCall()的說明。注意調(diào)用CancelBlockingCall()將使當(dāng)前操作失敗,GetLastError()函數(shù)返回WSAEINTR(指出是中斷操作)。
下面就是使用這個類的例子: ...
CTimeOutSocket sockServer; CAcceptedSocket sockAccept; sockServer.Create(777); sockServer.Listen(); // Note the following sequence: // SetTimeOut // <operation which might block> // KillTimeOut if(!sockServer.SetTimeOut(10000)) { ASSERT(FALSE); // Error Handling...for some reason, we could not setup // the timer. } if(!sockServer.Accept(sockAccept)) { int nError = GetLastError(); if(nError==WSAEINTR) AfxMessageBox("No Connections Arrived For 10 Seconds"); else ; // Do other error processing. } if(!sockServer.KillTimeOut()) { ASSERT(FALSE); // Error Handling...for some reason the timer could not // be destroyed...perhaps a memory overwrite has changed // m_nTimerID? // } ...
下面是示例代碼:
// // HEADER FILE // class CTimeOutSocket : public CSocket { public: BOOL SetTimeOut(UINT uTimeOut); BOOL KillTimeOut(); protected: virtual BOOL OnMessagePending(); private: int m_nTimerID; }; // // END OF FILE // // // IMPLEMENTATION FILE // BOOL CTimeOutSocket::OnMessagePending() { MSG msg; if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) { if (msg.wParam == (UINT) m_nTimerID) { // Remove the message and call CancelBlockingCall. ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); CancelBlockingCall(); return FALSE; // No need for idle time processing. }; }; return CSocket::OnMessagePending(); } BOOL CTimeOutSocket::SetTimeOut(UINT uTimeOut) { m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL); return m_nTimerID; } BOOL CTimeOutSocket::KillTimeOut() { return KillTimer(NULL,m_nTimerID); }
我按照以上方式建類并完成代碼。運行測試后發(fā)現(xiàn)并沒有實現(xiàn)效果!OnMessagePending沒有監(jiān)測到WM_TIMER消息。
然后我對類進行了調(diào)整,一來使得封裝更好,二來外部調(diào)用基本不用做任何變化,三來希望能使OnMessagePending能夠起作用。
其實修改很簡單,就是將SetTimeOut和KillTimeOut都修改為私有函數(shù),并重載CSocket基類的Receive和Send函數(shù),在收發(fā)前后啟動和關(guān)閉定時器。
class CTimeOutSocket : public CSocket{// Attributespublic: // Operationspublic: CTimeOutSocket(); virtual ~CTimeOutSocket(); // Overridespublic: // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CTimeOutSocket) public: virtual BOOL OnMessagePending(); virtual int Receive(void* lpBuf, int nBufLen, int nFlags = 0); virtual int Send(const void* lpBuf, int nBufLen, int nFlags = 0); //}}AFX_VIRTUAL // Generated message map functions //{{AFX_MSG(CTimeOutSocket) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG // Implementationprotected: int m_nTimerID;private: BOOL KillTimeOut(); BOOL SetTimeOut(int nTimeOut);}; BOOL CTimeOutSocket::OnMessagePending() { MSG msg; if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) { if (msg.wParam == (UINT) m_nTimerID) { // Remove the message and call CancelBlockingCall. ::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE); CancelBlockingCall(); return FALSE; // No need for idle time processing. }; }; return CSocket::OnMessagePending();} int CTimeOutSocket::Receive(void* lpBuf, int nBufLen, int nFlags) { SetTimeOut(10000); int nRecv = CSocket::Receive(lpBuf, nBufLen, nFlags); KillTimeOut(); return nRecv;} int CTimeOutSocket::Send(const void* lpBuf, int nBufLen, int nFlags) { SetTimeOut(10000); int nSend = CSocket::Send(lpBuf, nBufLen, nFlags); KillTimeOut(); return nSend; } BOOL CTimeOutSocket::SetTimeOut(int nTimeOut){ m_nTimerID = SetTimer(NULL,0,nTimeOut,NULL); return m_nTimerID;} BOOL CTimeOutSocket::KillTimeOut(){ return KillTimer(NULL,m_nTimerID);}
經(jīng)過修改的代碼,運行后OnMessagePending得到了WM_TIMER消息,正確解決了Receive的阻塞問題,同時我只需將原來的CSocket對象改為CTimeOutSocket對象就可以了,其它代碼不用變化。
如對本文有疑問,請?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會為你解答??! 點擊進入論壇