• <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>

            我的IOCP網(wǎng)絡(luò)模塊設(shè)計

             

            為了設(shè)計一個穩(wěn)定易用高效的iocp網(wǎng)絡(luò)模塊,我前前后后花了好幾個月的時間,也曾閱讀過網(wǎng)上很多資料和代碼,但是非常遺憾,能找到的資料一般都說得很含糊,很少有具體的,能找到的代碼離真正能商用的網(wǎng)絡(luò)模塊差得太遠(yuǎn),大多只是演示一下最基本的功能,而且大多是有很多問題的,主要問題如下:

            1、 很多代碼沒有處理一次僅發(fā)送成功部分?jǐn)?shù)據(jù)的情況。

            2、 幾乎沒有找到能正確管理所有資源的代碼。

            3、 大多沒有采用用pool,有的甚至畫蛇添足用什么map查找對應(yīng)客戶端,沒有充分使用perhandle, perio

            4、 接收發(fā)送數(shù)據(jù)大多拷貝太多次數(shù)。

            5、 接收管理大多很低效,沒有充分發(fā)揮iocp能力。

            6、 幾乎都沒有涉及上層如何處理邏輯,也沒有提供相應(yīng)解決方案(如合并io線程處理或單獨邏輯線程)。

            7、 大多沒有分離流數(shù)據(jù)和包數(shù)據(jù)。

            問題還有很多,就不一一列出來了,有一定設(shè)計經(jīng)驗的人應(yīng)該有同感。要真正解決這些問題也不是那么容易的,特別是在win下用iocp的時候資源釋放是個麻煩的問題,我在資源管理上花了很多時間,起初也犯了很多錯誤,后來在減少同步對象上又花了不少時間(起初client用了兩個同步對象,后來減少為1個)。下面我就我所設(shè)計的網(wǎng)絡(luò)模塊的各個部分進(jìn)行簡單的講解

            一、內(nèi)存管理。

            內(nèi)存管理是采用池模式,設(shè)計了一個基礎(chǔ)池類,可以管理某固定大小的池

            class CBufferPool

            {

                   

                    void *newobj();

                    void delobj(void *pbuf);

                   

            };

            在基礎(chǔ)池類上提供了一個模板的對象池

            template <class T>

            class CObjPool : public CBufferPool

            {

            public:

                    T *newobj()

                    {

                            void *p = CBufferPool::newobj();

                            T *pt = new(p) T;

                            return pt;

                    }

                    void delobj(T* pt)

                    {

                            pt->~T();

                            CBufferPool::delobj(pt);

                    }

            };

             

            在基礎(chǔ)池的基礎(chǔ)上定義了一個簡單的通用池

            class CMemoryPool

            {

            private:

                    CBufferPool bp[N];

            };

            通用池是由N個不同大小的基礎(chǔ)池組成的,分配的時候圓整到合適的相近基礎(chǔ)池并由基礎(chǔ)池分配。

            最后還提供了一個內(nèi)存分配適配器類,從該類派生的類都支持內(nèi)存池分配。

            class t_alloc

            {

            public:

                    static void *operator new(size_t size)

                    {

                            return CMemoryPool::instance().newobj(size);

                    }

                    static void operator delete(void *p, size_t size)

                    {

                            CMemoryPool::instance().delobj(p, size);

                    }

            };

            根據(jù)測試CMempool分配速度比CObjpool<>稍微慢一點點,所以我在用的時候就直接用t_alloc類派生,而不是用對象池,這是個風(fēng)格問題,也許有很多人喜歡用更高效一點的objpool方式,但這個并不大礙。

             

            在網(wǎng)絡(luò)模塊中OVERLAPPED派生類就要用池進(jìn)行分配,還有CIocpClient也要用池分配,再就是CBlockBuffer也是從池分配的。

            如下定義:

            struct IOCP_ACCEPTDATA : public IOCP_RECVDATA, public t_alloc

            class CIocpClient : public t_alloc

             

            二、數(shù)據(jù)緩沖區(qū)。

            數(shù)據(jù)緩沖區(qū)CBlockBuffer為環(huán)形,大小不固定,隨便分配多少,主要有以下幾個元素:

            Char *pbase;           //環(huán)形首部

            Char *pread;           //當(dāng)前讀指針

            Char *pwrite;          //當(dāng)前寫指針

            Int nCapacity;         //緩沖區(qū)大小

            Long nRef;              //關(guān)聯(lián)計數(shù)器

            用這種形式管理緩沖區(qū)有很多好處,發(fā)送數(shù)據(jù)的時候如果只發(fā)送了部分?jǐn)?shù)據(jù)只要修改pread指針即可,不用移動數(shù)據(jù),接收數(shù)據(jù)并處理的時候如果只處理了部分?jǐn)?shù)據(jù)也只要修改pread指針即可,有新數(shù)據(jù)到達(dá)后直接寫到pwrite并修改pwrite指針,不用多次拷貝數(shù)據(jù)。nRef關(guān)聯(lián)計數(shù)還可處理一個包發(fā)給N個人的問題,如果要給N個人發(fā)送相同的包,只要分配一個緩沖區(qū),并設(shè)置nRefN就可以不用復(fù)制N份。

             

            三、收發(fā)緩沖區(qū)管理

            發(fā)送緩沖區(qū)

            我把CIocpClient的發(fā)送數(shù)據(jù)設(shè)計為一個CBlockBuffer 的隊列,如果隊列內(nèi)有多個則WSASend的時候一次發(fā)送多個,如果只有一個則僅發(fā)送一個,CIocpClient發(fā)送函數(shù)提供了兩個,分別是:

            Bool SendData(char *pdata, int len);

            Bool SendData(CBlockBuffer *pbuffer);

            第一個函數(shù)會檢測發(fā)送鏈的最后一個數(shù)據(jù)塊能否容納發(fā)送數(shù)據(jù),如果能復(fù)制到最后一個塊,如果不能則分配一個CBlockBuffer掛到發(fā)送鏈最后面,當(dāng)然這個里面要處理同步。

             

            接收緩沖區(qū)

            接收管理是比較簡單的,只有一個CBlockBufferWSARecv的時候直接指向CBlockBuffer->pwrite,所以如果塊大小合適的話基本上是不用拼包的,如果一次沒有收到一個完整的數(shù)據(jù)包,并且塊還有足夠空間容納剩余空間,那么再提交一個WSARecv讓起始緩沖指向CBlockBuffer->pwrite如此則收到一個完整數(shù)據(jù)包的過程都不用重新拼包,收到一個完整數(shù)據(jù)包之后可以調(diào)用虛函數(shù)讓上層進(jìn)行處理。

            IocpClient層其實是不支持?jǐn)?shù)據(jù)包的,在這個層次只有流的概念,這個后面會專門講解。

             

            四、IocpServer的接入部分管理

            我把IocpServer設(shè)計為可以支持打開多個監(jiān)聽端口,對每個監(jiān)聽端口接入用戶后調(diào)用IocpServer的虛函數(shù)分配客戶端:

                    virtual CIocpClient *CreateNewClient(int nServerPort)

            分配客戶端之后會調(diào)用IocpClient的函數(shù) virtual void OnInitialize();分配內(nèi)部接收和發(fā)送緩沖區(qū),這樣就可以根據(jù)來自不同監(jiān)聽端口的客戶端分配不同的緩沖區(qū)和其他資源。

             

            Accept其實是個可以有很多選擇的,最簡單的做法可以用一個線程+accept,當(dāng)然這個不是高效的,也可以采用多個線程的領(lǐng)導(dǎo)者-追隨者模式+accept實現(xiàn),還可以是一個線程+WSAAccept,或者多個線程的領(lǐng)導(dǎo)者-追隨者模式+WSAAccept模式,也可以采用AcceptEx模式,我是采用AcceptEx模式做的,做法是有接入后投遞一個AcceptEx,接入后重復(fù)利用此OVERLAPPED再投遞,這樣即使管理大量連接也只有起初的幾十個連接會分配 OVERLAPPED后面的都是重復(fù)利用前面分配的結(jié)構(gòu),不會導(dǎo)致再度分配。

             

            IocpServer還提供了一個虛函數(shù)

                    virtual bool CanAccept(const char *pip, int port){return true;}

            來管理是否接入某個ip:port 的連接,如果不接入直接會關(guān)閉該連接并重復(fù)利用此前分配的WSASocket

             

            五、資源管理

            Iocp網(wǎng)絡(luò)模塊最難的就是這個了,什么時候客戶端關(guān)閉或服務(wù)器主動關(guān)閉某個連接并收回資源,這是最難處理的問題,我嘗試了幾種做法,最后是采用計數(shù)器管理模式,具體做法是這樣的:

            CIocpClient2個計數(shù)變量

                    volatile long m_nSending;              //是否正發(fā)送中

                    volatile long m_nRef;                     //發(fā)送接收關(guān)聯(lián)字

            m_nSending表示是否有數(shù)據(jù)已WSASend中沒有返回

            m_nRef表示WSASendWSARecv有效調(diào)用未返回和

            在合適的位置調(diào)用

                    inline void AddRef(const char *psource);

                    inline void Release(const char *psource);

            增引用計數(shù)和釋放引用計數(shù)

                    if(InterlockedDecrement(&m_nRef)<=0)

                    {

                            //glog.print("iocpclient %p Release %s ref %d\r\n", this, psource, m_nRef);

                            m_server->DelClient(this);

                    }

            當(dāng)引用計數(shù)減少到0的時候刪除客戶端(其實是將內(nèi)存返回給內(nèi)存池)。

             

            六、鎖使用

            鎖的使用至關(guān)重要,多了效率低下,少了不能解決問題,用多少個鎖在什么粒度上用鎖也是這個模塊的關(guān)鍵所在。

            IocpClient有一個鎖      DECLARE_SIGNEDLOCK_NAME(send);        //發(fā)送同步鎖

            這個鎖是用來控制發(fā)送數(shù)據(jù)鏈管理的,該鎖和前面提到的volatile long m_nSending;共同配合管理發(fā)送數(shù)據(jù)鏈。

            可能有人會說recv怎么沒有鎖同步,是的,recv的確沒有鎖,recv不用鎖是為了最大限度提高效率,如果和發(fā)送共一個鎖則很多問題可以簡化,但沒有充分發(fā)揮iocp的效率。Recv接收數(shù)據(jù)后就調(diào)用OnReceive虛函數(shù)進(jìn)行處理。可以直接io線程內(nèi)部處理,也可以提交到某個隊列由獨立的邏輯線程處理。具體如何使用完全由使用者決定,底層不做任何限制。

             

            七、服務(wù)器定時器管理

            服務(wù)器定義了如下定時器函數(shù),利用系統(tǒng)提供的時鐘隊列進(jìn)行管理。

                    bool AddTimer(int uniqueid, DWORD dueTime, DWORD period, ULONG nflags=WT_EXECUTEINTIMERTHREAD);

                    bool ChangeTimer(int uniqueid, DWORD dueTime, DWORD period);

                    bool DelTimer(int uniqueid);

                    //獲取Timers數(shù)量

                    int GetTimerCount() const;

                    TimerIterator GetFirstTimerIterator();

                    TimerNode *GetNextTimer(TimerIterator &it);

                    bool IsValidTimer(TimerIterator it)

            設(shè)計思路是給每個定時器分配一個獨立的id,根據(jù)id可修改定時器的首次觸發(fā)時間和后續(xù)每次觸發(fā)時間,可根據(jù)id刪除定時器,也可遍歷定時器。定時器時間單位為毫秒。

             

            八、模塊類結(jié)構(gòu)

            模塊中最重要的就是兩個類CIocpClientCIocpServer,其他有幾個類從這兩個類派生,圖示如下:

             

            圖表 1

             

            圖表 2

             

            CIocpClient是完全流式的,沒有包概念。CIocpMsgClientCIocpClient派生,內(nèi)部支持包概念:

            class CIocpMsgClient : public CIocpClient

            {

                    virtual void OnDataTooLong(){};

                    virtual void OnMsg(PKHEAD *ph){};

             

                    bool SendMsg(WORD mtype, WORD stype, const char *pdata, int length);

            };

             

            template <class TYPE>

            class CIocpMsgClientT : public CIocpMsgClient

            {

                    void AddMsg(DWORD id, CBFN pfn);

                    BOOL DelMsg(DWORD id);

            };

            CIocpMsgClientT模板類支持內(nèi)嵌入式定義,如在

            CMyDoc中可這樣定義

            CIocpMsgClientT<CMyDoc> client;

            后面可以調(diào)用client.AddMsg(UMSG_LOGIN, OnLogin);關(guān)聯(lián)一個類成員函數(shù)作為消息處理函數(shù),使用很方便。

             

            CIocpServerT定義很簡單,從CIocpServer派生,重載了CreateNewClient函數(shù)

            template <class TClient>

            class CIocpServerT : public CIocpServer

            {

            public:

                    //如果CIocpClient派生了則也需要重載下面的函數(shù),這里可以根據(jù)nServerPort分配不同的CIocpClient派生類

                    virtual CIocpClient *CreateNewClient(int nServerPort)

                    {

                            CIocpClient *pclient = new TClient;

                            return pclient;

                    }

            };

             

            八、應(yīng)用舉例

             

            class CMyClient : public CIocpMsgClient

            {

            public:

                    CMyClient() : CIocpMsgClient()

                    {

                    }

                    virtual ~CMyClient()

            {

            }

                    virtual void OnConnect()

            {

            Printf(“用戶連接%s:%d連接到服務(wù)器\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnClose()

            {

            Printf(“用戶%s:%d關(guān)閉連接\r\n”, GetPeerAddr().ip(),GetPeerAddr().port());

            }

                    virtual void OnMsg(PKHEAD *phead)

            {

                    SendData((const char *)phead, phead->len+PKHEADLEN);

            }

                    virtual void OnSend(DWORD dwbyte)

            {

            Printf(“成功發(fā)送%d個字符\r\n”, dwbyte);

            }

                    virtual void OnInitialize()

            {

                    m_sendbuf = newbuf(1024);

                    m_recvbuf = newbuf(4096);

            }

             

                    friend class CMyServer;

             

            };

             

            class CMyServer : public CIocpServer

            {

            public:

                    CMyServer() : CIocpServer

                    {

                    }

             

                    virtual void OnConnect(CIocpClient *pclient)

            {

                            printf("%p : %d 遠(yuǎn)端用戶%s:%d連接到本服務(wù)器.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnClose(CIocpClient *pclient)

            {

                    printf("%p : %d 遠(yuǎn)端用戶%s:%d退出.\r\n", pclient, pclient->m_socket,

                                    pclient->GetPeerAddr().ip(), pclient->GetPeerAddr().port());

            }

                    virtual void OnTimer(int uniqueid)

            {

            If(uniqueid == 10)

            {

            }

            Else if(uniqueid == 60)

            {

            }

            }

            //這里可以根據(jù)nServerPort分配不同的CIocpClient派生類

                    virtual CIocpClient *CreateNewClient(int nServerPort)

                    {

                    //      If(nServerPort == ?)

            //             

                            CIocpClient *pclient = new CMyClient;

                            return pclient;

                    }

             

            };

             

            Int main(int argc, char *argv[])

            {

                    CMyServer server;

             

                    server.AddTimer(60, 10000, 60000);

                    server.AddTimer(10, 10000, 60000);

             

                    //第二個參數(shù)為0表示使用默認(rèn)cpu*2io線程,>0表示使用該數(shù)目的io線程。

            //第三個參數(shù)為0表示使用默認(rèn)cpu*4個邏輯線程,如果為-1表示不使用邏輯線程,邏輯在io線程內(nèi)計算。>0則表示使用該數(shù)目的邏輯線程

                    server.StartServer("1000;2000;4000", 0, 0);

            }

             

            從示例可看出,對使用該網(wǎng)絡(luò)模塊的人來說非常簡單,只要派生兩個類,集中精力處理消息函數(shù)即可,其他內(nèi)容內(nèi)部全部包裝了。

             

            九、后記

            我研究iocp大概在2005年初,前一個版本的網(wǎng)絡(luò)模塊是用多線程+異步事件來做的,iocp網(wǎng)絡(luò)模塊基本成型在2005年中,后來又持續(xù)進(jìn)行了一些改進(jìn),2005底進(jìn)入穩(wěn)定期,2006年又做了一些大的改動,后來又持續(xù)進(jìn)行了一些小的改進(jìn),目前該模塊作為服務(wù)程序框架已經(jīng)在很多項目中穩(wěn)定運行了1年半左右的時間。在此感謝大寶、Chost ChengSunway等眾多網(wǎng)友,是你們的討論給了我靈感和持續(xù)改進(jìn)的動力,也是你們的討論給了我把這些寫出來的決心。若此文能給后來者們一點點啟示我將甚感欣慰,若有錯誤歡迎批評指正。

             

            oldworm

            oldworm@21cn.com

            2007.9.24

            Posted on 2010-10-03 14:25 袁斌 閱讀(918) 評論(0)  編輯 收藏 引用 所屬分類: 游戲開發(fā)
            男女久久久国产一区二区三区| 久久91综合国产91久久精品| 久久久人妻精品无码一区| 国产精品久久久久乳精品爆| 热久久国产欧美一区二区精品| 久久热这里只有精品在线观看| 久久国产精品无码一区二区三区| 国产成人精品久久| 无码久久精品国产亚洲Av影片| 色综合久久最新中文字幕| 久久精品国产男包| 国产午夜精品久久久久九九电影| 中文字幕热久久久久久久| 色成年激情久久综合| 伊人久久大香线蕉综合Av| 精品水蜜桃久久久久久久| 国产综合久久久久| 国产精品美女久久福利网站| 国产69精品久久久久99尤物| 久久亚洲日韩精品一区二区三区| 一本久久精品一区二区| 久久se这里只有精品| 1000部精品久久久久久久久| 国内精品久久久久影院薰衣草| 精品久久久久久无码人妻蜜桃| 国产精品视频久久| 久久国产高潮流白浆免费观看| 中文国产成人精品久久亚洲精品AⅤ无码精品| aaa级精品久久久国产片| 日产精品久久久久久久性色| 久久婷婷五月综合成人D啪| 四虎国产精品免费久久| 久久中文字幕视频、最近更新| 99热都是精品久久久久久| 久久香蕉一级毛片| 日本精品久久久久中文字幕| 狠色狠色狠狠色综合久久| 久久久九九有精品国产| 狠狠色伊人久久精品综合网| 久久e热在这里只有国产中文精品99 | 日本精品久久久久中文字幕|