最近參加了中興公司的通信軟件設計大賽,開發一個模擬手機和基站信令通信的軟件,遇到CSocket發送阻塞的問題,這裏有一個簡單的解決方案供大家參考。
CSocket繼承自CAsyncSocket,他們的不同是前者是同步套接字,後者是異步套接字,操作都是異步的。Socket中的Receive、Send 和Connect是阻塞操作,我們知道阻塞操作的特點是要麼運行成功退出,要麼出現錯誤退出,而且有的時候如果不成功,則該函數會一直阻塞,整個程序會一直等待操作的完成。
本文提出的解決方法是通過編程限制完成操作使用的時間,設置定時,設定一定的超時時間,如果阻塞操作時間過程,則啓動該超時機制。雖然操作是阻塞操作,但是我們可以處理操作中到達的消息。如果通過使用 SetTimer 設置定時器,那麼可以查找 WM_TIMER 消息,並在收到該消息時終止操作。
這種方法比較常見,也比較容易想到,網上也有很多相關的思想。設置定時的方法有::SetTimer函數,具體參加msdn。處理消息過程是CSocket中的CSocket::OnMessagePending 函數,退出阻塞的函數是CSocket中的CSocket::CancelBlockingCall 函數。我們在使用的時候往往是定義自己的CClientSocket類繼承自CSocket類,這樣這些函數都可以獲得了。
本人通過網上的資料得知,這種方法目前僅適用於 Visual C++ 的 1.52、1.52b、2.1 和 2.2 版本。
相關函數如下:
相關函數如下:
BOOL SetTimeOut(UINT uTimeOut)
調用此函數之後僅接着調用 CSocket 函數(如 Receive、Send 和 Accept)。uTimeOut 參數是以毫秒爲單位指定的。之後,進行定時器的設置。如果設置定時器失敗,那麼函數返回 FALSE。有關詳細情況,請參閱 SetTimer 函數的 Windows API 文檔。BOOL KillTimeOut()
在完成阻塞操作後,必須調用此函數。此函數刪除用 SetTimeOut 設置的定時器。如果調用 KillTimer 失敗,則返回 FALSE。有關詳細情況,請參閱 KillTimer 函數的 Windows API 文檔。BOOL OnMessagePending()
這是一個虛擬回調函數,在等待操作完成時由 CSocket 類進行調用。此函數給您提供處理傳入消息的機會。此實施過程檢查用 SetTimeOut 調用函數設置的定時器的 WM_TIMER 消息。如果收到消息,則調用 CancelBlockingCall 函數。有關 OnMessagePending 和 CancelBlockingCall 函數詳細的信息,請參閱 MFC 文檔。請注意:調用 CancelBlockingCall 函數 將導致操作失敗,而且 GetLastError 函數返回 WSAEINTR(表示操作中斷)。相關實現如下:
頭文件
class CClientSocket : public CSocket
{
//friend CClientThread;
// Attributes
public:
//CMCSServerDlg* m_dlgServer;
//CWinThread* m_pThread;
private:
int m_nTimerID;
// Overrides
public:
BOOL KillTimeOut();
BOOL SetTimeOut(UINT uTimeOut);
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CClientSocket)
public:
virtual void OnReceive(int nErrorCode);
virtual void OnClose(int nErrorCode);
virtual BOOL OnMessagePending();
//}}AFX_VIRTUAL
{
//friend CClientThread;
// Attributes
public:
//CMCSServerDlg* m_dlgServer;
//CWinThread* m_pThread;
private:
int m_nTimerID;
// Overrides
public:
BOOL KillTimeOut();
BOOL SetTimeOut(UINT uTimeOut);
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CClientSocket)
public:
virtual void OnReceive(int nErrorCode);
virtual void OnClose(int nErrorCode);
virtual BOOL OnMessagePending();
//}}AFX_VIRTUAL
//設置超時
BOOL CClientSocket::SetTimeOut(UINT uTimeOut)
{
m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL);
return m_nTimerID;
}
//取消設置超時
BOOL CClientSocket::KillTimeOut()
{
return KillTimer(NULL,m_nTimerID);
}
//用於CSocket函數阻塞時,如果超時,則退出該阻塞函數
BOOL CClientSocket::OnMessagePending()
{
MSG msg;
if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) {
if (msg.wParam == (UINT) m_nTimerID) {
::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);
CancelBlockingCall();
return FALSE;
};
};
return CSocket::OnMessagePending();
}
BOOL CClientSocket::SetTimeOut(UINT uTimeOut)
{
m_nTimerID = SetTimer(NULL,0,uTimeOut,NULL);
return m_nTimerID;
}
//取消設置超時
BOOL CClientSocket::KillTimeOut()
{
return KillTimer(NULL,m_nTimerID);
}
//用於CSocket函數阻塞時,如果超時,則退出該阻塞函數
BOOL CClientSocket::OnMessagePending()
{
MSG msg;
if(::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_NOREMOVE)) {
if (msg.wParam == (UINT) m_nTimerID) {
::PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);
CancelBlockingCall();
return FALSE;
};
};
return CSocket::OnMessagePending();
}
使用這種機制的代碼如下:
if(!sockServer.SetTimeOut(10000))
{
// 錯誤處理,不能建立定時
}
if(!sockServer.Accept(sockAccept))
{
int nError = GetLastError();
if(nError==WSAEINTR)
//重傳處理等等
else
//錯誤處理
}
if(!sockServer.KillTimeOut())
{
//不能取消定時,錯誤處理
}
{
// 錯誤處理,不能建立定時
}
if(!sockServer.Accept(sockAccept))
{
int nError = GetLastError();
if(nError==WSAEINTR)
//重傳處理等等
else
//錯誤處理
}
if(!sockServer.KillTimeOut())
{
//不能取消定時,錯誤處理
}
網上的另一種實現:
// 自己計算時間的辦法 OK
public:
BOOL SetTimeOut(UINT uTimeOut=1000);
BOOL KillTimeOut();
private:
LONGLONG m_llDtStart;
UINT m_uTimeOut;
};
/////////////////////////////////////////////////////////////////////////////
//`AFX_INSERT_LOCATION`
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)
// TimeOutSock.cpp : implementation file
//
#include "stdafx.h"
#include "NetBroad.h"
#include "TimeOutSock.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock
CTimeOutSock::CTimeOutSock()
{
}
CTimeOutSock::~CTimeOutSock()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CTimeOutSock, CSocket)
//{{AFX_MSG_MAP(CTimeOutSock)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock member functions
//設置超時
BOOL CTimeOutSock::SetTimeOut(UINT uTimeOut)
{
//get start cnt
LARGE_INTEGER llCnt;
::QueryPerformanceCounter(&llCnt);
m_llDtStart=llCnt.QuadPart;
m_uTimeOut=uTimeOut;
return TRUE;
}
//刪除超時參數
BOOL CTimeOutSock::KillTimeOut()
{
m_llDtStart=0;//表明取消計時
return TRUE;
}
//檢查是否超時間
BOOL CTimeOutSock::OnMessagePending()
{
// TODO: Add your specialized code here and/or call the base class
/*
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.
};
};
*/
if( m_llDtStart )
{
LARGE_INTEGER lldtEnd;
::QueryPerformanceCounter(&lldtEnd);
LARGE_INTEGER llFrq;
::QueryPerformanceFrequency(&llFrq);
double dbDealy=(double)(lldtEnd.QuadPart-m_llDtStart)*1000/llFrq.QuadPart;
if( dbDealy>m_uTimeOut )
{
CancelBlockingCall();
return FALSE; // No need for idle time processing.
}
}
return CSocket::OnMessagePending();
}
public:
BOOL SetTimeOut(UINT uTimeOut=1000);
BOOL KillTimeOut();
private:
LONGLONG m_llDtStart;
UINT m_uTimeOut;
};
/////////////////////////////////////////////////////////////////////////////
//`AFX_INSERT_LOCATION`
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TIMEOUTSOCK_H__19897A81_4EAF_4005_91FD_DC3047725139__INCLUDED_)
// TimeOutSock.cpp : implementation file
//
#include "stdafx.h"
#include "NetBroad.h"
#include "TimeOutSock.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock
CTimeOutSock::CTimeOutSock()
{
}
CTimeOutSock::~CTimeOutSock()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CTimeOutSock, CSocket)
//{{AFX_MSG_MAP(CTimeOutSock)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CTimeOutSock member functions
//設置超時
BOOL CTimeOutSock::SetTimeOut(UINT uTimeOut)
{
//get start cnt
LARGE_INTEGER llCnt;
::QueryPerformanceCounter(&llCnt);
m_llDtStart=llCnt.QuadPart;
m_uTimeOut=uTimeOut;
return TRUE;
}
//刪除超時參數
BOOL CTimeOutSock::KillTimeOut()
{
m_llDtStart=0;//表明取消計時
return TRUE;
}
//檢查是否超時間
BOOL CTimeOutSock::OnMessagePending()
{
// TODO: Add your specialized code here and/or call the base class
/*
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.
};
};
*/
if( m_llDtStart )
{
LARGE_INTEGER lldtEnd;
::QueryPerformanceCounter(&lldtEnd);
LARGE_INTEGER llFrq;
::QueryPerformanceFrequency(&llFrq);
double dbDealy=(double)(lldtEnd.QuadPart-m_llDtStart)*1000/llFrq.QuadPart;
if( dbDealy>m_uTimeOut )
{
CancelBlockingCall();
return FALSE; // No need for idle time processing.
}
}
return CSocket::OnMessagePending();
}