关于CSocket类的Receive超时的问题

前几日碰到一问题,当CSocket的Receive阻塞时,如何进行超时处理。由于程序是在多线程中使用Socket通信,开始时是在主线程中用定时监测Receive函数,当超时后,结束通信。但问题是CSocket对象无法释放。因此从网上搜索解决办法,直接在线程中对Receive进行超时处理。

不错,搜到以下内容,很多网站转载。

 

为CSocket配置Time-Out功能
    CSocket操作,如Send(),Receive(),Connect()都属阻塞操作,即它们在成功完成或错误发生之前是不会返回的。
    在某些情况下,某项操作可能永远不能成功完成,程序为了等待其完成就得永远循环下去。在程序中为某项操作限定一个成功完成的时间是个好主意。本文就是讨论此问题的。
    一个办法是设计一个计时器,当操作费时过长时就触发。这个办法的关键是怎样处理计时器。虽然操作是"阻塞"的,但仍具处理传回的消息的能力。如果用SetTimer来设置计时器,就可截获WM_TIMER消息,当它产生时就终止操作。涉及到这个过程的主要函数是:Windows API ::SetTimer(),MFC函数CSocket::OnMessagePending()和CSocket:: CancelBlockingCall()。这些功能可包装到你的CSocket类中得以简化。
    类中用到三个重要函数:
    BOOL SetTimeOut(UINT uTimeOut) 它应在CSocket函数调用前被调用。uTimeOut以千分秒为单位。下面的实现只是简单的设置计时器。当设置计时器失败时返回False。参见Windows API中关于SetTimer的说明。
    BOOL KillTimeOut() 此函数应在操作未完成被阻塞时被调用。它删除SetTimeOut所设置的计时器。如果调用KillTimer失败则返回False。参见Windows API中关于KillTimer的说明。
    BOOL OnMessagePending() 它是一个虚拟回调函数,当等待操作完成时被CSocket类调用。它给你机会来处理传回的消息。这次我们用它来检查SetTimeOut所设置的计时器,如果超时(Time-Out),则它调用CancelBlockingCall()。参见MFC文档关于OnMessagePending()和CancelBlockingCall()的说明。注意调用CancelBlockingCall()将使当前操作失败,GetLastError()函数返回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);
   }

 

我按照以上方式建类并完成代码。运行测试后发现并没有实现效果!OnMessagePending没有监测到WM_TIMER消息。

然后我对类进行了调整,一来使得封装更好,二来外部调用基本不用做任何变化,三来希望能使OnMessagePending能够起作用。

其实修改很简单,就是将SetTimeOut和KillTimeOut都修改为私有函数,并重载CSocket基类的Receive和Send函数,在收发前后启动和关闭定时器。

class CTimeOutSocket : public CSocket
{
// Attributes
public:

// Operations
public:
 CTimeOutSocket();
 virtual ~CTimeOutSocket();

// Overrides
public:
 // 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

// Implementation
protected:
 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);
}

经过修改的代码,运行后OnMessagePending得到了WM_TIMER消息,正确解决了Receive的阻塞问题,同时我只需将原来的CSocket对象改为CTimeOutSocket对象就可以了,其它代码不用变化。

你可能感兴趣的:(timer,windows,api,null,mfc,attributes)