• <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>
            aurain
            技術文摘
            posts - 137,  comments - 268,  trackbacks - 0

            愿文:http://blog.csdn.net/rabbit729/archive/2009/02/23/3928437.aspx
            網絡游戲服務器開發技術

             -------如何正確高效的使用內存和對象內存池?
               大家都知道,游戲服務器在網絡游戲開發中所占的比重。而評論游戲服務器的好壞標準,除了實現游戲的邏輯功能外,最重要的也就是穩定和高效。一個不穩定的服務器對于一款網絡游戲的打擊是沉重,一個不高效的服務器對于玩家的感覺也是非常明顯的。
            在這一章節中,我將要向大家介紹游戲服務器高效開發的一個方面,如何正確高效的使用內存?而關于其他高效開發的技術或者架構設計,我也將陸續向大家介紹。其中有欠妥之處,也希望讀者告訴我,我們大家一起成長。
            先簡單說下內存和內存使用的通俗概念,內存也就是一塊虛擬地址空間。而在C++程序開發過程中,我們可以直接讀\寫這個地址空間,也就是內存使用。

            接下來我們來了解下內存的分配方式。通常情況下,內存的分配方式有3種方式:
            1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內
            存在程序的整個運行期間都存在。例如全局變量,static變量。
            2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧
            上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
            3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new
            申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。
               
            使用點評A:
            在游戲服務器開發過程中,如果我們要在函數中需要一塊不大于4M的內存,建議我們使用第2)種分配方式。因為這種方式不僅可以使應用程序高效(分配快,且不會造成內存碎片),而且會防止內存泄露。(有時我們的程序員會在不經意間忘記掉釋放自己通過new或者malloc分配的內存))

            對于三種分配方式,也就對應了三種不同的內存生命周期。所謂生命周期也就是從產生到釋放的一個過程時間段。

            1) 靜態分配的內存空間的生命周期期是:整個軟件運行期,就是說從軟
            件運行開始到軟件終止退出。只有軟件終止運行后,這塊內存才會被系統回收 。

            2) 棧中分配的內存空間的生命周期是和相應的函數或者內存對象的作
            用域有關。
                 例如函數如下:
            void Func(void)
            {
            { int i = 10; i++;}
               int j = 0;
            for(;j<100;j++){ printf("%d\n",j);}
            }
            對于上面函數中兩個局部變量,雖然都是從棧中進行分配,但生命周期
            是不一樣的,變量i出了花括號{}后被系統回收,所以在函數其他地方將是無意義的。而變量j在函數結束時被系統回收。但他們都有一個共同點:那就是使用者不需要去關心如何進行分配和釋放,所有這一切都是被C++所有保證的。

            3) 在堆上分配的內存,生命周期是從調用new或者malloc開始,到調
            用delete或者free結束。如果不調用用delete或者free。則這塊空間必須到軟件終止運行后才能被系統回收。
            使用點評B:
            在我們寫程序過程中如果要在堆中分配內存,必須養成一個良好的習慣。那就是我們首先必須建立成對的new\delete和malloc\free。對于其他使用此內存對象進行邏輯處理的代碼就直接放在其中間就可以了。

               
            最后我們再來看看,我們通常使用內存比較容易犯的一些錯誤。其實大家可不要小看這些錯誤,很多時候這些錯誤的產生,對于產品使用者的影響是巨大的。
             內存未分配成功,我們就錯誤的使用了它。
            這種情況的發生。就通常和我們沒有養成良好的編程習慣或者經驗不豐富有關。常用解決辦法是,在使用內存之前檢查指針是否為NULL。如果指針p是函數的參數,那么在函數的入口處用if(p!=NULL)進行檢查。如果我們在函數中使用malloc或new來申請內存,在使用前必須使用if(p!=NULL)判斷后,再進行使用。
             內存分配雖然成功,但是尚未初始化就引用它。
            犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為內存的缺省初值全為零,導致引用初值錯誤(例如數組)。
            內存的缺省初值究竟是什么并沒有統一的標準,盡管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
             內存分配成功并且已經初始化,但操作越過了內存的邊界。
            這種情況的產生經常會發生在數組下標的錯誤使用和對象指針的錯誤內存拷貝。

            例如:

            void Func(void)
            {
                   char chArray[100];
                 for(int j=1;j<=100;j++){ chArray[j] = 100; }
            }

            void Func(void)
            {
               char *p = (char*)malloc(10);
               char *pszA = "DFASDFASDFASDFASDFASDFFD";
               memcpy(p,pszA,strlen(pszA));
               free(p);
            }

             忘記了內存釋放,造成內存泄露。
                 對于這種錯誤的產生原因可就多種多樣了,而最常見犯錯誤的原因,還是大家沒有一個良好的編程習慣或者對于指針使用理解不深刻造成。

                 下面簡單列舉大家可能犯錯誤的幾種常見情況:
                 錯誤1:
                   void AllocateMemory(char *pStr, int num)
            {
            pStr =  new char[num];
            }
                 錯誤2:
            void Func(void)
            {
            CObj *pOBJ = new CObj[100];
            if(pStr!=NULL)
            {
            .........
            }
            delete pOBJ;
            }
                 使用點評C:
                錯誤1沒有很好的理解函數對于參數的處理是為每一個參數設置一個副本,在函數中語句操縱的其實是參數的副本,而本身并沒有被改變。
                錯誤2沒有將[]配對使用,造成99個CObj對象的析構函數沒有被調用,對象本身內存泄露,且程序會報告異常。
             釋放了內存卻繼續使用它。
            有幾種情況:
             程序中的對象調用關系過于復雜,實在難以搞清楚某個對象究竟是否已經釋放了內存,此時應該重新設計程序結構,理清楚對象直接的關系,解決程序混亂的問題。
             函數的return語句寫錯了,注意不要返回指向“棧內存”的“指針”或者“引用”,因為該內存在函數體結束時被自動銷毀。
             使用free或delete釋放了內存后,沒有將指針設置為NULL。導致產生“野指針”。
             錯誤的使用棧內存,造成棧溢出。
            如果我們在函數中需要一定大小的內存,從棧中分配要比new或者malloc堆內存分
            配方式要快很多,所以我們很多朋友就肆無忌憚的使用棧內存。從而造成棧溢出。
            “An unhandled exception of type 'System.StackOverflowException' occurred in Temp.exe“
                 這種情況的產生,多數是大家沒有很好的了解內存對象生命周期而造成的。
                 例如:
            void Func(void)
            {
            for(int i=0;i<10;i++){ char szBuf[1024*1024]; .....}
            }
            使用點評D:
            1.使用指針之前,首先必須檢查指針指向內存的有效性。防止使用NULL指針,造成程序崩潰。
            2.分配內存成功后,最好要對于內存賦初值。防止將未被初始化的內存作為右值使用,造成程序的錯誤。
            3.避免數組下標或者指針的地址越界,特別要當心發生“多1”、“少1”和內存拷貝的操作。
            4.動態內存的申請與釋放必須配對,防止內存泄漏。同時不能夠將new和free或者malloc和delete進行錯誤配對。因為這樣產生的問題將是難以預料的。
            5.用free或delete釋放了內存之后,立即將指針設置為NULL,杜絕產生“野指針”。
            6.合理的使用棧內存,并且需要關心局部內存對象的生命周期.從而無錯高效的使用
            棧內存.
             
            上面用了如此多的篇幅來向大家講述內存的分配、釋放和使用注意點。只是想加深大家對于內存使用的理解。下面我們就來繼續討論我們在游戲服務器中如何正確高效使用內存和建立內存管理池。

            首先,我先簡單的敘述下一般游戲服務器在資源使用方面,都需要做些什么?以下簡單歸納為如下幾種資源使用情況:
            a) 啟動Listen端口偵聽的Client連接請求。在Client連接成功后,分配一定的
            對象資源和此連接對應。
            b) 接受Client的網絡連接斷開請求,釋放與此連接所對應的所有資源。
            c) 接受Client網絡數據包,跟據協議碼處理此數據包。處理并且釋放協議包資源。
            d) 接受DB訪問,提出SQL請求、獲取記錄集、處理記錄集,然后釋放記錄集資源等。
            e) 地圖Monster對象的產生和銷毀.NPC對象產生和銷毀等等。
            ………………
            細心的我們,觀察以上幾點。我們不難發現,在游戲服務器開發過程中,將涉及到大量的內存資源分配和釋放。
            而對于MMO或者其他休閑網絡游戲產品所涉及的各種服務器:longinserver、gamegate、dbserver、gameserver等,由于各自要處理的邏輯和擔任角色不一樣,其設計復雜度和代碼量也不盡相同,這其中以gameserver為其中之最。代碼量通常會在幾萬行到幾十萬行不等,而其設計復雜度更是根據不同游戲而定。

            以上說的只是他們的開發差異,而所有服務器程序必須具備的共同點是:穩定和高性能。同時我們不僅要求單個服務器的穩定和高性能,而是要求全局服務器組的穩定和高性能。因此,這就要求我們服務器程序員必須具體比較強的代碼控制能力和豐富的開發經驗。而這其中一個比較重要環節就是內存的合理使用。
            如果我們使用傳統的內存分配方式來進行程序設計,情況將會如下所示:
            使用的時候向系統申請,使用后就歸還給系統。也正是我們所說的new\delete和malloc\free這種方式。
            服務器應用程序開啟一段時間之后,我們的系統使用中內存和空閑內存將會呈現如下方式分布:
             
            這其中將會出現許多內存碎片,由于內存碎片的大量存在,我們服務器性能也會隨之降低。并且我們還必須承擔由于內存使用不當造成系統不穩定或者內存大量泄露的風險。
            既然傳統的內存編程方式在服務器程序開發和應用過程中有這樣的一些潛在弊端。那么我們使用方式來改進我們的服務器應用程序性能和增強系統穩定性呢?
            大家不妨嘗試使用,我接下來大家要的內容: 對象內存池技術.
            大家一定要說了,內存池技術前面為什么要加上“對象”呢。其實就是想和我們平常所說的內存池技術概念進行區別,在這里向大家講述的是狹義的內存池技術,也就是應用層面的內存池技術。

            提示:
            在VC6.0以上開發環境中,我們是基于OOP進行游戲服務器開發。在OOP(面向對象編程)編程過程中我們習慣的將我們應用程序中遇到的所有一切進行抽象,成為一個概念對象。
            例如:gameserver 中的玩家,我們抽象為class CPlayer,怪物,抽象為class CMonster,網絡協議包消息,抽象為class CNetEvent等等。
            所以可以簡單的說,我們的游戲服務器編程也就是對象編程。而具體這些基礎知識和抽象技巧,這里也不多累贅了。

            在這里,還是先介紹下我們這邊對象內存池技術的基本概念和設計過程:
             
            所謂的對象內存池技術設計過程如下:
            首先為某種對象預先生成若干個空閑對象,并且使用對象管理類進行管理。應用程序在需要使用此對象時,即向管理對象申請空閑對象.管理對象即檢視對象內存池,如果發現存在未使用空閑對象,即分配給申請者。如果發現已無空閑對象,可自行擴充對象內存池,并且滿足申請對象的需求,也可以直接返回NULL,表明對象申請失敗。在程序獲取對象并且使用后,想釋放此對象資源時。繼續想管理對象提出申請釋放對象,管理對象接受到釋放對象后將其再次放入對象池,成為可使用對象。

            看了上面的介紹后,接下來以偽代碼的方式來更加清晰的展現對象獲取和釋放的過程。
                申請對象
            OBJ*  ApplyObj(void)
            {
            if 存在空閑對象
            {
            OBJ *pIdleObj = NULL;
            pIdleObj = GetIdleObj(); //獲取空閑對象
            return pIdleObj;

               //不存在空閑對象,處理方式如下
            方式1:
            ExtendObjectPool();        //擴充對象池
            OBJ *pIdleObj = NULL;
            pIdleObj = GetIdleObj();  //獲取空閑對象
            return pIdleObj;
            方法2:
            return NULL;

            釋放對象
            void ReleaseObj(OBJ* pObj)
            {
            if(pObj!=NULL)
            {
                    AddToObjectPool(pObj);   //對象再次加入到對象池

            有了上面的這些說明,我想大家對于對象內存池技術應該都有了一個大概了解吧!(其實沒有什么高深的技術,只是一些簡單的應用,大家用一個平常心來看待就可以了!)接下介紹具體來實現這個對象內存池,我們需要做些什么?

            在實現對象內存池之前,先提出幾個我們需要達到的目標:

             對象內存池管理對象具有廣泛的通用性,也就是說能夠滿足應用程序生成各個不同的對象池,例如:Player對象池、Monster對象池、NPC對象池。
             產生對象的速度一定要快于直接使用new\delete或者malloc\free方式很多倍。
             對象池容量具有可擴展性和糾錯能力。也就是說在無空閑對象時,管理對象類能夠自動生成一批新的空閑對象供上層使用,同時能夠正確指出目前所使用對象是否為合法對象池對象。
             對象的申請和釋放,必須具備多線程安全性。也就是說在服務器程序通過各個不同線程同時訪問對象池管理時,能夠保證合法獲取和釋放池對象。


            基于上面這些問題條件,這里先提供幾種簡單易行的解決方案來供大家參考。其他解決方案還有很多,我也就不一一列舉了!靠大家獨立思考和發揮了。:)

            解決方案1:(單鏈表實現)
             第一步,分配模板對象數組,并且用指針保存。
             第二步,建立一單鏈表管理類,將已經分配成功的數組對象,分配到蛋鏈表中,同時設置表頭和表尾指針。
             第三步,從對象池中申請對象,首先檢測鏈表中是否存在可使用空閑對象。如果沒有可返回NULL,也可以先鎖定擴充鏈表(保存擴展對象數組指針)。然后返回可使用對象給用戶。
             第四步,釋放對象池對象時,首先將表尾指針指向被釋放對象,接下來被回收對象為此鏈表表尾。完成釋放過程。
             最后,內存釋放,delete數組指針。
            圖例演示如下:

            解決方案2:(雙鏈表實現)
             為了能夠使我們建立的對象池能夠在應用程序中通用,我們考慮使用模板template<class OBJ>來生成我們的class CObjectPool.
             為了能夠快速獲取對象,我們采用雙向鏈表的方式來建立我們的對象池。對象獲取從當前鏈表頭開始進行,對象釋放直接加到鏈表尾。操作過程中需要使用一附加指針對象表明當前可使用對象位置。若此指針為NULL,表明已無可使用空閑對象。
             為了生成一個一定容量的對象池,我們可以通過模板的方式也可以通過初始化Init方式來生成初始對象池。在申請過程中無空閑對象,需要向系統重新一定數目對象,并且按照順序加到鏈表尾。實現對象池的可擴充性。
             為了保證多線程安全,我們在對象申請和釋放過程中加入Lock進行鎖定,保證每次只有一個線程操作對象池。
               
            以上就為此對象池實現的解決方案,在了解到實現過程的前提下,具體實現代碼這里也就不累贅了。大家可以發揮實現,如果實現過程中出現問題可以直接和我聯系。

            重申:

            游戲服務器編程不是一個多高深的程序設計課題,設計和開發高質量的網絡游戲服務器程序也不是那么的可怕和難以攀登。要的是我們在開發過程中能夠多吸收前人所積累的經驗,杜絕將前人錯誤進行重演。同時要的是我們扎實的C++基礎功底、認真嚴謹的開發態度和對于游戲開發事業的120%熱情。我們決不要做被程序開發左右的人,要做左右程序開發的人(汗中….)。另外提醒游戲服務器開發,力求穩定實用,個人不建議在代碼中使用過多的技巧,同時STL部分建議適量使用。

             

            本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/rabbit729/archive/2009/02/23/3928437.aspx

            posted on 2009-06-17 00:38 閱讀(1584) 評論(0)  編輯 收藏 引用 所屬分類: 內存管理

            <2008年9月>
            31123456
            78910111213
            14151617181920
            21222324252627
            2829301234
            567891011

            常用鏈接

            留言簿(17)

            隨筆分類(138)

            隨筆檔案(137)

            網絡開發

            最新隨筆

            搜索

            •  

            積分與排名

            • 積分 - 499202
            • 排名 - 36

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            狠狠色丁香婷婷综合久久来来去| 欧美精品久久久久久久自慰| 岛国搬运www久久| 一本色道久久HEZYO无码| 天天综合久久久网| 少妇久久久久久久久久| 国产免费福利体检区久久| 性做久久久久久久久| 亚洲伊人久久大香线蕉苏妲己| 香蕉久久av一区二区三区| 亚洲国产精品婷婷久久| 国产精品99久久久精品无码| 日本欧美国产精品第一页久久| 伊人久久大香线蕉av不变影院| 88久久精品无码一区二区毛片| 久久99久国产麻精品66| 久久精品国产亚洲AV不卡| AV无码久久久久不卡网站下载| 国产综合久久久久久鬼色| 天天做夜夜做久久做狠狠| 精品综合久久久久久97超人 | 久久久久中文字幕| 狠狠色综合网站久久久久久久高清| 日韩亚洲欧美久久久www综合网| 亚洲AV无码久久| 国产aⅴ激情无码久久| 99热精品久久只有精品| 狠狠久久亚洲欧美专区| 久久精品日日躁夜夜躁欧美| 久久精品国产一区二区三区不卡| 久久久久亚洲精品无码网址| 亚洲伊人久久综合影院| 久久久精品国产sm调教网站| 777久久精品一区二区三区无码| 久久国产精品一国产精品金尊| 欧美丰满熟妇BBB久久久| 久久毛片一区二区| 嫩草影院久久国产精品| 久久精品无码一区二区无码 | 国内精品久久久久久麻豆| 久久国产精品-国产精品|