五月综合缴情婷婷六月,色94色欧美sute亚洲线路二,日韩制服国产精品一区,色噜噜一区二区三区,香港三级午夜理伦三级三

您現(xiàn)在的位置: 365建站網(wǎng) > 365文章 > C#中Socket.Receive()的超時阻塞問題

C#中Socket.Receive()的超時阻塞問題

文章來源:365jz.com     點擊數(shù):5871    更新時間:2018-06-08 22:38   參與評論

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)友會為你解答??! 點擊進入論壇

發(fā)表評論 (5871人查看0條評論)
請自覺遵守互聯(lián)網(wǎng)相關(guān)的政策法規(guī),嚴(yán)禁發(fā)布色情、暴力、反動的言論。
昵稱:
最新評論
------分隔線----------------------------

其它欄目

· 建站教程
· 365學(xué)習(xí)

業(yè)務(wù)咨詢

· 技術(shù)支持
· 服務(wù)時間:9:00-18:00
365建站網(wǎng)二維碼

Powered by 365建站網(wǎng) RSS地圖 HTML地圖

copyright © 2013-2024 版權(quán)所有 鄂ICP備17013400號