[WinSock]封装WSAAsyncSelect!
封裝目標: 最終目標是封裝WinSock的WSAAsyncSelect?IO模型。
封裝原則: 耦合性[減少各種依賴,包括classes之間,編譯模塊之間。],小粒度增加可復用性。
依賴ATL/WTL。?
CKxAsyncSocket?是我要實現的class,它只需要維護SOCKET成員,另外因為是異步選擇,所以可以再維護一個HWND。
關于HWND,是否有必要暴露給用戶,關鍵在于Socket要僅僅運行于UI主線程,還是用戶可以自己創建另一個有消息循環的Thread,在其上處理Socket的消息。
我選擇后者,因為,盡管異步的Socket,在Recv大數據的時候,還是可能導致UI“卡”的。
?
那么,我們就需要一個線程,一個有處理消息的窗口,二者形成一個UI線程, 然后再該UI線程處理Socket消息。以上提到的幾個實體,實體之間要盡量低耦合,無依賴。
【通常來說,接口耦合和可以接受的,我們也會通過一些技巧來實現低耦合】
1. 線程只需要一個簡單的CreateThread封裝,Like Java Style的就好,支持Runnable接口,好處就不說了。
2. 處理消息的窗口,我們用WTL,搞個簡單的:
#define?CHAIN_MSG_MAP_MEMBER_PTR(theChainMemberPtr)?\????{?\
????????if(?NULL?!=?theChainMemberPtr?&&?\
????????????theChainMemberPtr->ProcessWindowMessage(hWnd,?uMsg,?wParam,?lParam,?lResult))?\
????????????return?TRUE;?\
????}
????
class?MessageOnlyWindow?:?public?CWindowImpl<MessageOnlyWindow,?CWindow,?CWinTraits<>?>
{
????public:
????????BEGIN_MSG_MAP(MessageOnlyWindow)
????????????CHAIN_MSG_MAP_MEMBER_PTR(?m_pWinMsgHandler?)
????????END_MSG_MAP()
????????MessageOnlyWindow(){}
????????~MessageOnlyWindow()
????????{
????????????::DestroyWindow(m_hWnd);
????????????m_hWnd?=?NULL;
????????}
????????HWND?Create(ProcessWindowMessageHandler*?pWinMsgHandler)
????????{
????????????m_pWinMsgHandler?=?pWinMsgHandler;
????????????return?CWindowImpl<MessageOnlyWindow,?CWindow,?CWinTraits<>?>::Create(HWND_MESSAGE);
????????}
????private:
????????ProcessWindowMessageHandler*?m_pWinMsgHandler;
};??
需要說明一下:
CHAIN_MSG_MAP_MEMBER_PTR是仿WTL的宏,WTL缺少一個處理成員指針的CHAIN*. 這樣 HWND_MESSAGE窗口的消息,都可以投遞給 ProcessWindowMessageHandler*?m_pWinMsgHandler了。?
PS:
class?__declspec(novtable)?ProcessWindowMessageHandler{
public:
????virtual?BOOL?ProcessWindowMessage(HWND?hWnd,?UINT?uMsg,?WPARAM?wParam,?LPARAM?lParam,?LRESULT&?lResult,?DWORD?dwMsgMapID?=?0)?=?0;
};
//?ProcessWindowMessage和BEGIN_MSG_MAP的聲明是一致的,但是處理成虛函數了。這樣可以多態到派生類,但是又不會依賴派生類(的聲明)
//?WTL大量依賴模板來處理這種關系。?
那么現在,我們再創建一個類,表達UI Thread的概念即可,它可以繼承或者聚合一個CKxThread即可。然后在線程中創建窗口和消息循環即可:
virtual?int?Run(?CKxThread*?pThread?)?{
????MessageOnlyWindow?msgWnd;
????HWND?hWnd?=?msgWnd.Create(m_pRoutine);
????m_pRoutine->OnBegin(?pThread,?hWnd?);
????MSG?msg;
????while(?GetMessage(&msg,?NULL,?0,?0)?)
????{
????????TranslateMessage(?&msg?);
????????DispatchMessage(?&msg?);
????}
????return?0;
}?// 代碼有節略.
再看下m_pRoutine的類型:
?
class?__declspec(novtable)?IKxMessageRoutine?:?public?ProcessWindowMessageHandler?
實際上用戶自己派生的
IKxMessageRoutine ,就可以通過Windows Message Thread實例傳遞給 MessageOnlyWindow 。這樣用戶的派生類就可以直接Handle這個Thread的消息了,看一下這個派生類的樣子:?
class?CUserThreadMessageHandler?:?public?IKxMessageRoutine,?public?CKxAsyncSocketMessageMixin{
private:
????BEGIN_MSG_MAP( CUserThreadMessageHandler)
????????CHAIN_MSG_MAP(CKxAsyncSocketMessageMixin)
? ? END_MSG_MAP()??
首先,我們可以確定的是,
CUserThreadMessageHandler 僅僅是消息的處理者,而且和Socket事件沒有必然聯系,如果不繼承 CKxAsyncSocketMessageMixin (忽略此處的Public繼承)的話。因為Windows中,除了異步選擇的Socket IO模型,還有很多依賴消息循環的編程模型。所以不考慮第二個繼承關系的 CUserThreadMessageHandler 是一個很好的處理UI 線程消息的對象。在該線程中,需要處理的消息,可以按照WTL的消息映射寫處理函數。?
那么,我們現在要處理Socket的事件了,好,加一個Mixin即可【當然了,要通過
CHAIN_MSG_MAP 來把CKxAsyncSocketMessageMixin接入到消息隊列中】,這樣,一個Socket綁定了這個線程的消息窗口后,就可以在這個線程的消息循環中工作了。?
CKxAsyncSocketMessageMixin是一個處理Socket事件的類,并且把SOCKET<->Socket對象指針進行映射【沒辦法,OS投遞SOcket事件的時候,參數是SOCKET,而不是別的,所以自己要做這個Mapping,搞過WinSock封裝的人,都清楚這點。HWND<->CWnd*的映射也是一般道理?!?/p> class?CKxAsyncSocketMessageMixin?
{
public:
????
????BEGIN_MSG_MAP(CKxAsyncSocketMessageMixin)
????????MESSAGE_HANDLER(HK_WM_SOCKET,?OnSocketMessage)
...?...
//?HK_WM_SOCKET就是WinSock?API?WSAAsyncSelect的第三個參數,你懂的。?
最后,Socket對象也就可以收到消息了。然后根據消息轉到不同的處理函數即可,OnConnect,OnRecv...[并且我的Socket,也實行了ProcessWindowMessageHandler接口]
-------------------------------------------------------------------------------------------------------------------------------------------------------
// Socket 本身相關的.?
[WinSock]其實和WTL關系不大,但是我會用到一些WTL的東西。
?
因為是異步選擇,所以需要一個窗口來接收消息,于是...
class?MessageOnlyWindow?:?public?CWindowImpl<MessageOnlyWindow,?CWindow,?CWinTraits<>?>{
????BEGIN_MSG_MAP(MessageOnlyWindow)
????MESSAGE_HANDLER(WM_SOCKET,?OnSocketMessage)
????END_MSG_MAP()
? ? HWND Create()
{ return CWindowImpl<MessageOnlyWindow, CWindow, CWinTraits<> >::Create(HWND_MESSAGE);?}?
};
這樣,非常簡單的搞定了消息窗口【可算是和WTL扯上關系了】
異步Socket的Receive才是我關注的:
[1]? ? ? ?
WSAAsyncSelect自動把一個阻塞的socket轉為非阻塞的,如果需要轉為阻塞的,那么先要調用這個函數,并且(最后一個參數long lEvent設置為0) 。然后調用ioctlsocket,或者WSAIoctl。?
// MSDN:The?WSAAsyncSelect?function automatically sets socket?s?to?nonblocking?mode, regardless of the value of?lEvent. To set socket?s?back to blocking mode, it is first?necessary?to clear the event record associated with socket?s?via a call to?WSAAsyncSelect?with?lEvent?set to?zero. You can then call?ioctlsocket?or?WSAIoctl?to set the socket back to blocking mode.
設置為阻塞的:
?
u_long?iMode?=?0;????//?set?blocking.????ioctlsocket(?m_hSocket,?FIONBIO,?&iMode?);
?
[2]?
一旦設置了阻塞,我想,還是加個超時的好:
?
DWORD?dwRecvTimeout?=?5000;?//?5?sec?timeout;? ? setsockopt(?m_hSocket,?SOL_SOCKET,?SO_RCVTIMEO,?(char*)&dwRecvTimeout,?sizeof(DWORD)?);
?
總結
以上是生活随笔為你收集整理的[WinSock]封装WSAAsyncSelect!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库的同步和复制----sql语句方法
- 下一篇: interface接口实例