我修正的gh0st控制端的bug

来自:高性能服务器开发(微信号:easyserverdev),作者:张小方

gh0st_server 源码分析

gh0st_server 使用的框架是 MFC,部分界面元素使用了 CJ60Lib。关于界面部分,我们这里就不做介绍了。

笔者修正的 bug

原始的 gh0st_server 代码由于在网络线程中直接操作 UI 元素,这在 CJ60Lib 中会导致程序崩溃。我们来看一下原始的逻辑:

//IOCPServer.cpp 868行
bool CIOCPServer::OnClientReading(ClientContext* pContext, DWORD dwIoSize)
{
    CLock cs(CIOCPServer::m_cs, "OnClientReading");
    try
    {
        //////////////////////////////////////////////////////////////////////////
        static DWORD nLastTick = GetTickCount();
        static DWORD nBytes = 0;
        nBytes += dwIoSize;

        if (GetTickCount() - nLastTick >= 1000)
        {
            nLastTick = GetTickCount();
            InterlockedExchange((LPLONG)&(m_nRecvKbps), nBytes);
            nBytes = 0;
        }

        //////////////////////////////////////////////////////////////////////////

        if (dwIoSize == 0)
        {
            RemoveStaleClient(pContext, FALSE);
            return false;
        }

        if (dwIoSize == FLAG_SIZE && memcmp(pContext->m_byInBuffer, m_bPacketFlag, FLAG_SIZE) == 0)
        {
            // 重新发送
            Send(pContext, pContext->m_ResendWriteBuffer.GetBuffer(), pContext->m_ResendWriteBuffer.GetBufferLen());
            // 必须再投递一个接收请求
            PostRecv(pContext);
            return true;
        }

        // Add the message to out message
        // Dont forget there could be a partial, 1, 1 or more + partial mesages
        pContext->m_CompressionBuffer.Write(pContext->m_byInBuffer, dwIoSize);

        m_pNotifyProc((LPVOID)m_pFrame, pContext, NC_RECEIVE);


        // Check real Data
        while (pContext->m_CompressionBuffer.GetBufferLen() > HDR_SIZE)
        {
            BYTE bPacketFlag[FLAG_SIZE];
            CopyMemory(bPacketFlag, pContext->m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));

            if (memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag)) != 0)
                throw "bad buffer";

            //nSize是包的总大小
            int nSize = 0;
            //CopyMemory(&nSize, pContext->m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(int));
            CopyMemory(&nSize, pContext->m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(bPacketFlag));

            // Update Process Variable
            pContext->m_nTransferProgress = pContext->m_CompressionBuffer.GetBufferLen() * 100 / nSize;

            if (nSize && (pContext->m_CompressionBuffer.GetBufferLen()) >= nSize)
            {
                int nUnCompressLength = 0;
                // Read off header
                pContext->m_CompressionBuffer.Read((PBYTE)bPacketFlag, sizeof(bPacketFlag));

                pContext->m_CompressionBuffer.Read((PBYTE)&nSize, sizeof(int));
                pContext->m_CompressionBuffer.Read((PBYTE)&nUnCompressLength, sizeof(int));

                ////////////////////////////////////////////////////////
                ////////////////////////////////////////////////////////
                // SO you would process your data here
                // 
                // I'm just going to post message so we can see the data
                int    nCompressLength = nSize - HDR_SIZE;
                PBYTE pData = new BYTE[nCompressLength];
                PBYTE pDeCompressionData = new BYTE[nUnCompressLength];

                if (pData == NULL || pDeCompressionData == NULL)
                    throw "bad Allocate";

                pContext->m_CompressionBuffer.Read(pData, nCompressLength);

                //////////////////////////////////////////////////////////////////////////
                unsigned long    destLen = nUnCompressLength;
                int    nRet = uncompress(pDeCompressionData, &destLen, pData, nCompressLength);
                //////////////////////////////////////////////////////////////////////////
                if (nRet == Z_OK)
                {
                    pContext->m_DeCompressionBuffer.ClearBuffer();
                    pContext->m_DeCompressionBuffer.Write(pDeCompressionData, destLen);
                    m_pNotifyProc((LPVOID)m_pFrame, pContext, NC_RECEIVE_COMPLETE);
                }
                else
                {
                    throw "bad buffer";
                }

                delete[] pData;
                delete[] pDeCompressionData;
                pContext->m_nMsgIn++;
            }
            else
                break;
        }
        // Post to WSARecv Next
        PostRecv(pContext);
    }
    catch (...)
    {
        pContext->m_CompressionBuffer.ClearBuffer();
        // 要求重发,就发送0, 内核自动添加数包标志
        Send(pContext, NULL0);
        PostRecv(pContext);
    }

    return true;
}

上述代码中 40 行有一行调用:

m_pNotifyProc((LPVOID)m_pFrame, pContext, NC_RECEIVE);

这是一个函数指针,在 CMainFrame::Activate 函数中初始化:

//MainFrm.cpp 330行
void CMainFrame::Activate(UINT nPort, UINT nMaxConnections)
{
    CString     str;
    if (m_iocpServer != NULL)
    {
        m_iocpServer->Shutdown();
        delete m_iocpServer;
    }
    m_iocpServer = new CIOCPServer;

    // 开启IPCP服务器
     if (m_iocpServer->Initialize(NotifyProc, this100000, nPort))
     {
        //...无关代码省略...
     }
     //...无关代码省略...
 }

上述第 13 行即初始化这个函数指针的代码:

//IOCPServer.cpp 124行
bool CIOCPServer::Initialize(NOTIFYPROC pNotifyProc, CMainFrame* pFrame, int nMaxConnections, int nPort)
{
    m_pNotifyProc = pNotifyProc;

    //...无关代码省略...

    return false;
}

这样网络线程中调用 m_pNotifyProc 实际上是调用的是 CMainFrame::NotifyProc,这个函数原始的代码是这样的:

//MainFrm.cpp 239行
void CALLBACK CMainFrame::NotifyProc(LPVOID lpParam, ClientContext *pContext, UINT nCode)
{
    //减少无效消息
    if (pContext == NULL || pContext->m_DeCompressionBuffer.GetBufferLen() <= 0)
        return;

    try
    {
        CMainFrame* pFrame = (CMainFrame*) lpParam;
        CString str;
        // 对g_pConnectView 进行初始化
        g_pConnectView = (CGh0stView *)((CGh0stApp *)AfxGetApp())->m_pConnectView;

        // g_pConnectView还没创建,这情况不会发生
        if (((CGh0stApp *)AfxGetApp())->m_pConnectView == NULL)
            return;

        g_pConnectView->m_iocpServer = m_iocpServer;
        str.Format(_T("S: %.2f kb/s R: %.2f kb/s"), (float)m_iocpServer->m_nSendKbps / 1024, (float)m_iocpServer->m_nRecvKbps / 1024);
        //g_pFrame->m_wndStatusBar.SetPaneText(1, str);


        switch (nCode)
        {
        case NC_CLIENT_CONNECT:
            break;
        case NC_CLIENT_DISCONNECT:
            g_pConnectView->PostMessage(WM_REMOVEFROMLIST, 0, (LPARAM)pContext);
            break;
        case NC_TRANSMIT:
            break;
        case NC_RECEIVE:
            ProcessReceive(pContext);
            break;
        case NC_RECEIVE_COMPLETE:
            ProcessReceiveComplete(pContext);
            break;
        }
    }catch(...){}
}

这样就出现了在网络线程(工作线程)中直接操作 UI 元素,这在 CJ60Lib 这个库中是不允许的,会导致程序崩溃。我作了如下修改:

  1. 在 CMainFrame::NotifyProc 通过 PostMessage 产生一个 UI 更新事件 WM_UPDATE_MAINFRAME 发给 UI 线程(主线程),数据通过 WM_UPDATE_MAINFRAME 消息的 WPARAM 和 LPARAM 两个参数传递;

    void CALLBACK CMainFrame::NotifyProc(LPVOID lpParam, ClientContext *pContext, UINT nCode)
    {
       //减少无效消息
       if (pContext == NULL || pContext->m_DeCompressionBuffer.GetBufferLen() <= 0)
           return;

       CMainFrame* pFrame = (CMainFrame*)lpParam;
       pFrame->PostMessage(WM_UPDATE_MAINFRAME, (WPARAM)nCode, (LPARAM)pContext);
       //CJlib库不能在工作线程操作UI
       return;

       //使用return语句屏蔽无关的代码
    }
  2. 为消息 WM_UPDATE_MAINFRAME 注册消息处理函数 CMainFrame::NotifyProc2;

    ON_MESSAGE(WM_UPDATE_MAINFRAME, NotifyProc2) 
  3. 在 CMainFrame::NotifyProc2 根据消息携带的参数更新界面。

   LRESULT CMainFrame::NotifyProc2(WPARAM wParam, LPARAM lParam)
   {
       ClientContext* pContext = (ClientContext *)lParam;
       UINT nCode = (UINT)wParam;

       try
       {
           //CMainFrame* pFrame = (CMainFrame*)lpParam;
           CString str;
           // 对g_pConnectView 进行初始化
           g_pConnectView = (CGh0stView *)((CGh0stApp *)AfxGetApp())->m_pConnectView;

           // g_pConnectView还没创建,这情况不会发生
           if (((CGh0stApp *)AfxGetApp())->m_pConnectView == NULL)
               return 0;

           g_pConnectView->m_iocpServer = m_iocpServer;
           str.Format(_T("S: %.2f kb/s R: %.2f kb/s"), (float)m_iocpServer->m_nSendKbps / 1024, (float)m_iocpServer->m_nRecvKbps / 1024);
           m_wndStatusBar.SetPaneText(1, str);


           switch (nCode)
           {
           case NC_CLIENT_CONNECT:
               break;
           case NC_CLIENT_DISCONNECT:
               g_pConnectView->PostMessage(WM_REMOVEFROMLIST, 0, (LPARAM)pContext);
               break;
           case NC_TRANSMIT:
               break;
           case NC_RECEIVE:
               ProcessReceive(pContext);
               break;
           case NC_RECEIVE_COMPLETE:
               ProcessReceiveComplete(pContext);
               break;
           }
       }
       catch (...){}

       return 1;
   }
推荐↓↓↓
程序员的那点事
上一篇:财务和金融背景,27岁学编程晚不晚 下一篇:gh0st源码分析(中篇)