• [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, 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) );
  • 相关阅读:
    luogu P1415 拆分数列 序列DP
    [HAOI2015]树上操作
    [SHOI2012]魔法树
    [SCOI2010]连续攻击游戏
    [NOI2016]区间
    简单数论(一)
    iermu爱耳目
    李宇春:会跳舞的文艺青年
    文峰塔很安祥
    技术宅之flappy bird 二逼鸟
  • 原文地址:https://www.cnblogs.com/healerkx/p/2189526.html
Copyright © 2020-2023  润新知