心寄笔端 附庸风雅

甘草的技术博客

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

[接上篇]

封装目标: 最终目标是封装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, 00) )
    {
        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*的映射也是一般道理。】

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_SOCKETOnSocketMessage)
    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) );

 

 

posted on 2011-09-24 16:21  甘草  阅读(1541)  评论(0编辑  收藏  举报
Baidu
Google
心寄笔端
TEST
以后我会加上Power By的,先别介意