• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            大漠落日

            while(!dead) study++;
            posts - 46, comments - 126, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            FileZilla Server源碼分析(3)

            Posted on 2010-06-10 13:38 亂78糟 閱讀(2338) 評論(1)  編輯 收藏 引用 所屬分類: 開源
            這是分析的第三節,上一節主要講了一些和socket基礎操作相關的代碼,本節將分析核心代碼。

            Service.cpp   系統服務程序

            FileZillaServer可以選擇是否注冊成windows的服務程序,而這個服務程序的代碼就是由service.cpp文件實現的。
            WinMain是它的入口函數,在WinMain里依次完成了下面幾項任務:
            1. 參數解析
            2. 初始化某些數據比如端口
            3. 由SCM(服務控制管理器)啟動服務,入口為ServiceMain函數;如果服務不存在,進入步驟4
            4. 根據參數設置服務,例如安裝、啟動、卸載等。
            ServiceMain注冊ServiceCtrlHandler來處理服務的控制代碼,在回調函數ServiceCtrlHandler中自定義了128號控制碼,用于向窗口"FileZilla Server Helper Window"發送重新讀取配置的自定義消息WM_FILEZILLA_RELOADCONFIG
            serviceMain注冊Handler成功之后,就啟動自己的工作線程ServiceExecutionThread,線程里創建了CServer對象,然后實際流程交由CServer。之后進入線程的消息循環并等待killServiceEvent信號以退出線程終止服務。
            Service.cpp中KillService函數中有一個變量hMainWnd,它是在stdafx.h中聲明的,它具體是哪個窗口的句柄,干什么用,現在還是一無所知。


            Server.*  真正的帶頭大哥

            打開Server.h文件,開頭就可以看到許多類的前置聲明,類中聲明了眾多上一節提到的相關類對象(或集合如list),CServer類把所有核心類(線程和socket)集中起來使用。
            上面已經提到Service的工作線程中調用了CServer的Create函數,我們就先從這里入手吧。
            Create一開始就創建了一個窗口,標題為"FileZilla Server Helper Window",呵呵,是不是很熟悉?然后將這個窗口的句柄賦給全局變量hMainWnd,至此,終于大致了解了服務的框架骨骼。
            之后又是一大堆初始化操作,包括兩個定時器,需要特別注意的是創建服務線程CServerThread時候的提供的參數WM_FILEZILLA_SERVERMSG + index,這個參數用以線程間通信,再上一節已經有過描述,稍后再具體分析。
            在往下調用了CreateListenSocket函數,這個函數根據Options類中獲取的port、bindip、enablessl等參數創建監聽ftp客戶端連接的CListenSocket對象指針,并保存到m_ListenSocketList中。這里有一個很重要的函數ShowStatus,它的任務是將信息發送給admin窗口和記錄到log中。
            最后調用CreateAdminListenSocket函數創建監聽admin客戶端的socket,并存入m_AdminListenSocketList中。

            CServer類的分析暫時中斷一下,我們來分析上面涉及到的幾個相關類:CServerThread,CListenSocket,CControlSocket,CTransferSocket。

            CServerThread繼承自CThread,構造函數有個int型參數,用來標識具體哪個線程的消息。注意,CThread本身并不是一個直接繼承于任何線程類的類,它只是負責創建并管理線程的類。m_sInstanceList是static的成員變量,被所有的CServerThread對象共享,而且這個list存儲的第一個值用于管理SL,為了標識它,作者又添加了一個BOOL成員變量m_bIsMaster,同樣還有一個static臨界區變量m_GlobalThreadsync用來同步它。如果當前對象是master,那么它還擁有一個用于實現PASV模式的CExternalIpCheck的類對象m_pExternalIpCheck,缺省值是不采用PASV的。

            對CServerThread的重要的幾個Public成員函數分析一下功能:
            • GetExternalIP : 調用m_pExternalIpCheck獲取PASV的ip
            • AddSocket:給自己發送一個線程消息,該消息在OnThreadMessage函數中被處理,用來添加(SSL)socket連接

            對CServerThread重要的幾個非public成員函數分析一下功能:
            • AddNewSocket:將sokcet handle綁定到新new的CControlSocket對象socket上,并為當前socket分配一個唯一的用戶ID。分配函數CalcUserID不算高效,尤其是連接用戶數量比較大的時候再分配尤其明顯。之后調用SendNotification準備發送包含連接用戶的信息的消息給CServer,最后向連接的用戶發送歡迎信息。
            • SendNotification:這個函數將需要發送的數據加入待發送list中,最牛的是它可以自動調節發送的效率。不過我發現一處小BUG,可能作者自己也沒有注意到,這兩處設置線程優先級貌似反了:
            else if (m_pendingNotifications.size() > 150 && m_throttled < 2)
            {
                SetPriority(THREAD_PRIORITY_LOWEST);
                m_throttled 
            = 2;
            }
            else if (m_pendingNotifications.size() > 100 && !m_throttled)
            {
                SetPriority(THREAD_PRIORITY_BELOW_NORMAL);
                m_throttled 
            = 1;
            }

            • OnThreadMessage:線程消息處理函數,如添加刪除用戶,解析命令,傳輸,控制,計時器等。

            CListenSocket類功能很簡單,如果一個連接被accept,那么從服務線程CServerThread列表中找到負載最小的線程,然后調用的AddSocket函數,將這個連接交給這個CServerTread管理。

            CControlSocket類負責與客戶端交互。
            它有一個int型成員變量m_antiHammeringWaitTime,用來防止用戶攻擊(即無限次嘗試登錄),例如某用戶在60秒內連續嘗試登錄10次失敗,那么就把這個用戶加入ban列表中,比如3000秒內拒絕再次登錄等。AntiHammerIncrease 函數中對這個變量的算法沒看明白
            if (m_status.hammerValue > 2000)
                m_antiHammeringWaitTime 
            += 1000 * (int)pow(1.3, (m_status.hammerValue / 400- 5);
            在用戶登錄的時候就去檢測是否是“攻擊”,代碼如下:
                BOOL bResult = GetPeerName((SOCKADDR*)&sockAddr, &nSockAddrLen);
                    
            if (bResult)
                        m_pOwner
            ->AntiHammerIncrease(sockAddr.sin_addr.s_addr);

                    
            if (m_pOwner->m_pAutoBanManager->RegisterAttempt(htonl(sockAddr.sin_addr.s_addr)))
                    {
                        Send(_T(
            "421 Temporarily banned for too many failed login attempts"));
                        ForceClose(
            -1);
                        
            return FALSE;
                    }

            PassCommand函數處理所有的命令,如USER、LIST、PASV、STOR等。當收到STOR命令時,如果是PASV模式,那么調用m_transferstatus.socket->PasvTransfer(),否則新建一個CTransferSocket套接字賦給m_transferstatus.socket,然后調用SendTransferinfoNotification發送TRANSFERMODE_RECEIVE消息。不管哪種方式,最后還是通過調用CTransferSocket的InitTransfer函數實現文件傳輸。


            好了,現在讓我們恢復現場。
            CServer類的消息處理函數WindowProc,處理了各種消息,其中重要的是WM_DESTROYWM_FILEZILLA_SERVERMSG。前者通知并等待所有線程退出,關閉socket,銷毀資源,殺死定時器,做的都是清理工作。后者根據服務線程發送來的消息進入函數OnServerMessage中,這個函數處理了所有服務管理的消息。可以看到,很多消息最后都是通過m_pAdminInterface->SendCommand(2, 3, buffer, len)這句發送出去。CAdminInterface類管理CAdminSocket類的指針列表,SendCommand其實是調用CAdminSocket的SendCommand將消息發送出去。函數中對admin socket做了自動管理,如果操作失敗,就自動移除該socket。
            CheckForTimeout每10秒由CServer的定時器調用一次,檢測admin socket是否超時,如果超時,自動移除。CAdminSocket收到數據并解析成功之后,最終交由CServer的ProcessCommand處理,該函數再一次根據Options里的設置對線程、socket進行一次校驗和調整。

            我個人對ProcessCommand和SendCommand函數參數中的type或nID為int型有微議,因為這兩個參數實際只占用了不到8個字節,寫為int不利于理解,如果改成int8一眼就能看出來這個參數具體占用幾個字節。
            BOOL CAdminSocket::SendCommand(int nType, int nID, const void *pData, int nDataLength)
            {
                
            /**/
                t_data data;
                data.pData 
            = new unsigned char[nDataLength + 5];
                
            *data.pData = nType;     //nType目前版本只要不為0就是合法的協議類型,代碼中用到了1和2
                
            *data.pData |= nID << 2//nType和nID合用一個8字節
                data.dwOffset = 0;
                memcpy(data.pData 
            + 1&nDataLength, 4);
                
            /**/
            }

            下面重點分析一下ProcessCommand這個函數,用偽代碼比較直觀。
            BOOL CServer::ProcessCommand(CAdminSocket *pAdminSocket, int nID, unsigned char *pData, int nDataLength)
            {
                
            switch(nID)
                { 
                
            case 2:
                   
            if (!nDataLength)
                       
            //獲取服務器狀態
                   else
                       
            //設置服務器狀態并獲取
                   else
                       
            //send error:wrong protocol type
                   break;
                 
            case 3:
                    
            if (!nDataLength)
                       
            //send error
                    else if (*pData == USERCONTROL_GETLIST)
                       
            //計算并格式化所有已連接用戶的信息到unsigned char *buffer中并發送給admin
                       
            //這些數據顯示在admin UI下方的user list中
                    else if (*pData == USERCONTROL_KICK || *pData == USERCONTROL_BAN)
                        
            //*pData共5個字節,第一個為具體協議類型,后四個為userID。
                        
            //根據協議對userID進行操作,kick或者Ban掉。
                    else
                         
            //send error: wrong protocol type
                     break;
                
            case 5:
                     
            if (!nDataLength)
                        
            //讀取基本配置然后發送給admin
                     else if (*m_pOptions)
                        
            //解析配置字符串,創建初始化或調整CServerThread
                        
            //CreateListenSocket
                        
            //創建admin監聽sockets
                      break;
                 
            case 6:
                      
            if (!nDataLength)
                          
            //讀取user和group的權限配置
                      else
                          
            //解析權限配置發送給admin
                      break;
                 
            case 8:
                      pAdminSocket
            ->SendCommand(18, NULL, 0);
                      
            break;
                  
            default:
                      
            //send error: unknow command
                }
                
            return true;
            }

            這一節涵蓋了眾多核心代碼,上面的分析相對來說還是比較粗略,所以,后面幾節在對這些粗略和遺漏部分在做更為詳細深入的挖掘,本節到這里就結束了。
            因為都是看代碼時臨時寫入筆記的,所有的分析都很雜亂,希望以后我有時間可以畫一些圖,重新做一次整理。
            2010-7-22補充
            圖隨便畫了幾張,鏈接在此

            PS: 本來上周就可以貼出來了,可是因為安裝MAC系統造成C盤WINDOWS系統數據破壞無法啟動,重裝系統導致筆記丟失,這里只能補上,拖后了一周左右。

            Feedback

            # re: FileZilla Server源碼分析(3) [未登錄]  回復  更多評論   

            2012-03-19 16:34 by ww
            SendNotification:這個函數將需要發送的數據加入待發送list中,最牛的是它可以自動調節發送的效率。不過我發現一處小BUG,可能作者自己也沒有注意到,這兩處設置線程優先級貌似反了

            這個他在注釋中已經寫了
            // Check if main thread can't handle number of notifications fast enough, throttle thread if neccessary


            是要讓主系程 降低優先級的 并沒有作者所說的反了
            亚洲午夜无码久久久久小说| 久久棈精品久久久久久噜噜| 久久www免费人成看国产片| 久久91这里精品国产2020| 亚洲国产成人精品无码久久久久久综合| 久久国产亚洲精品| 激情伊人五月天久久综合| 久久精品国产福利国产琪琪| 久久久久久无码Av成人影院| 国产精品一区二区久久精品无码| 97香蕉久久夜色精品国产| 狠狠色丁香婷婷综合久久来| 中文字幕亚洲综合久久菠萝蜜| 精品久久久久久| 免费精品久久天干天干| 国产精品美女久久久久av爽 | 久久黄视频| 精品久久久久久无码中文字幕一区 | 亚洲伊人久久成综合人影院| 精品久久久噜噜噜久久久| 性做久久久久久久久浪潮| 久久国产精品波多野结衣AV| 久久国产精品-国产精品| 国产成人精品综合久久久久| 久久人人爽人人爽人人片AV东京热| 久久婷婷成人综合色综合| 久久久久久久精品成人热色戒| 久久久久久国产精品无码下载 | 国产精品无码久久四虎| 久久国产亚洲精品麻豆| 久久精品国产亚洲精品2020| 亚洲午夜久久久久妓女影院 | 国产精品99久久久久久董美香 | 一本久久久久久久| 亚洲综合婷婷久久| 国产亚洲美女精品久久久| 人人狠狠综合久久亚洲88| 97久久精品无码一区二区| 久久精品国产精品亚洲精品| 久久亚洲精品成人AV| 国产日产久久高清欧美一区|