小談Online-game服務器端設計(4)

在這一章節,我想談談關于服務器端的腳本的相關設計。因為在上一章節里面,談NPC智能相關的時候已經接觸到一些腳本相關的東東了。還是先來談談腳本的作用吧。
  在基于編譯的服務器端程序中,是無法在程序的運行過程中構建一些東西的,那么這個時候就需要腳本語言的支持了,由于腳本語言涉及到邏輯判斷,所以光提供一些函數接口是沒用的,還需要提供一些簡單的語法和文法解析的功能。其實說到底,任何的事件都可以看成兩個部分:第一是對自身,或者別的物件的數值的改變,另外一個就是將該事件以文字或者圖形的方式廣播出去。那么,這里牽扯到一個很重要的話題,就是對某一物件進行尋址。恩,談到這,我想將本章節分為三個部分來談,首先是服務器如何來管理動態創建出來的物件(服務器內存管理),第二是如何對某一物件進行尋址,第三則是腳本語言的組織和解釋。其實之所以到第四章再來談服務器的內存管理是因為在前幾章談這個的話,大家對其沒有一個感性的認識,可能不知道服務器的內存管理究竟有什么用。
4.1、服務器內存管理
  對于服務器內存管理我們將采用內存池的方法,也稱為靜態內存管理。其概念為在服務器初始化的時候,申請一塊非常大的內存,稱為內存池(Memory pool),同時也申請一小塊內存空間,稱為垃圾回收站(Garbage recollecting station)。其大體思路如下:當程序需要申請內存的時候,首先檢查垃圾回收站是否為空,如果不為空的話,則從垃圾回收站中找一塊可用的內存地址,在內存池中根據地址找到相應的空間,分配給程序用,如果垃圾回收站是空的話,則直接從內存池的當前指針位置申請一塊內存;當程序釋放空間的時候,給那塊內存打上已經釋放掉的標記,然后把那塊內存的地址放入垃圾回收站。
  下面具體談談該方法的詳細設計,首先,我們將采用類似于操作系統的段頁式系統來管理內存,這樣的好處是可以充分的利用內存池,其缺點是管理起來比較麻煩。嗯,下面來具體看看我們怎么樣來定義頁和段的結構:
typedef struct m_segment_s
  {
    struct m_segment_s *next; /* 雙線鏈表 + 靜態內存可以達到隨機訪問和順序訪問的目的,
                   真正的想怎么訪問,就怎么訪問。 */
struct m_segment_s *pre; int flags; // 該段的一些標記。
int start; // 相對于該頁的首地址。
int size; // 長度。
struct m_page_s *my_owner; // 我是屬于哪一頁的。
char *data; // 內容指針。
}m_segment_t;
  typedef struct m_page_s
  {
    unsigned int flags; /* 使用標記,是否完全使用,是否還有空余 */
int size; /* 該頁的大小,一般都是統一的,最后一頁除外 */
int end; /* 使用到什么地方了 */
int my_index; /* 提供隨機訪問的索引 */
m_segment_t *segments; // 頁內段的頭指針。
 }m_page_t;
  那么內存池和垃圾回收站怎么構建呢?下面也給出一些構建相關的偽代碼:
static m_page_t *all_pages;
// total_size是總共要申請的內存數,num_pages是總共打算創建多少個頁面。
void initialize_memory_pool( int total_size, int num_pages )
  {
    int i, page_size, last_size; // 算出每個頁面的大小。
page_size = total_size / num_pages; // 分配足夠的頁面。
all_pages = (m_page_t*) calloc( num_pages, sizeof(m_page_t*) );
    for ( i = 0; i < num_pages; i ++ )
    {
// 初始化每個頁面的段指針。
all_pages[i].m_segment_t = (m_segment_t*) malloc( page_size );
// 初始化該頁面的標記。
all_pages[i].flags |= NEVER_USED;
// 除了最后一個頁面,其他的大小都是page_size 大小。
all_pages[i].size = page_size;
// 初始化隨機訪問的索引。
all_pages[i].my_index = i;
// 由于沒有用過,所以大小都是0
all_pages[i].end = 0;
    }
// 設置最后一個頁面的大小。
if ( (last_size = total_size % num_pages) != 0 )
      all_pages[i].size = last_size;
  }
  下面看看垃圾回收站怎么設計:
int **garbage_station;
  void init_garbage_station( int num_pages, int page_size )
  {
    int i;
    garbage_station = (int**) calloc( num_pages, sizeof( int* ) );
    for ( i = 0; i < num_pages; i ++)
    {
// 這里用unsigned short的高8位來儲存首相對地址,低8位來儲存長度。
garbage_station[i] = (int*) calloc( page_size, sizeof( unsigned short ));
      memset( garbage_station[i], 0, sizeof( garbage_station[i] ));
    }
  }
  也許這樣的貼代碼會讓大家覺得很不明白,嗯,我的代碼水平確實不怎么樣,那么下面我來用文字方式來敘說一下大體的概念吧。對于段頁式內存管理,首先分成N個頁面,這個是固定的,而對于每個頁面內的段則是動態的,段的大小事先是不知道的,那么我們需要回收的不僅僅是頁面的內存,還包括段的內存,那么我們就需要一個二維數組來保存是哪個頁面的那塊段的地址被釋放了。然后對于申請內存的時候,則首先檢查需要申請內存的大小,如果不夠一個頁面大小的話,則在垃圾回收站里面尋找可用的段空間分配,如果找不到,則申請一個新的頁面空間。
  這樣用內存池的方法來管理整個游戲世界的內存可以有效的減少內存碎片,一定程度的提高游戲運行的穩定性和效率。
4.2、游戲中物件的尋址
  第一個問題,我們為什么要尋址?加入了腳本語言的概念之后,游戲中的一些邏輯物件,比如說NPC,某個ITEM之類的都是由腳本語言在游戲運行的過程中動態生成的,那么我們通過什么樣的方法來對這些物件進行索引呢?說得簡單一點,就是如何找到他們呢?有個很簡單的方法,全部遍歷一次。當然,這是個簡單而有效的方法,但是效率上的消耗是任何一臺服務器都吃不消的,特別是在游戲的規模比較大之后。
  那么,我們怎么來在游戲世界中很快的尋找這些物件呢?我想在談這個之前,說一下Hash Table這個數據結構,它叫哈希表,也有人叫它散列表,其工作原理是不是順序訪問,也不是隨機訪問,而是通過一個散列函數對其key進行計算,算出在內存中這個key對應的value的地址,而對其進行訪問。好處是不管面對多大的數據,只需要一次計算就能找到其地址,非常的快捷,那么弊端是什么呢?當兩個key通過散列函數計算出來的地址是同一個地址的時候,麻煩就來了,會產生碰撞,其的解決方法非常的麻煩,這里就不詳細談其解決方法了,否則估計再寫個四,五章也未必談得清楚,不過如果大家對其感興趣的話,歡迎討論。
  嗯,我們將用散列表來對游戲中的物件進行索引,具體怎么做呢?首先,在內存池中申請一塊兩倍大于游戲中物件總數的內存,為什么是兩倍大呢?防止散列表碰撞。然后我們選用物件的名稱作為散列表的索引key,然后就可以開始設計散列函數了。下面來看個例子:
static int T[] =
  {
    1, 87, 49, 12, 176, 178, 102, 166, 121, 193, 6, 84, 249, 230, 44, 163,
    14, 197, 213, 181, 161, 85, 218, 80, 64, 239, 24, 226, 236, 142, 38, 200,
    110, 177, 104, 103, 141, 253, 255, 50, 77, 101, 81, 18, 45, 96, 31, 222,
    25, 107, 190, 70, 86, 237, 240, 34, 72, 242, 20, 214, 244, 227, 149, 235,
    97, 234, 57, 22, 60, 250, 82, 175, 208, 5, 127, 199, 111, 62, 135, 248,
    174, 169, 211, 58, 66, 154, 106, 195, 245, 171, 17, 187, 182, 179, 0, 243,
    132, 56, 148, 75, 128, 133, 158, 100, 130, 126, 91, 13, 153, 246, 216, 219,
    119, 68, 223, 78, 83, 88, 201, 99, 122, 11, 92, 32, 136, 114, 52, 10,
    138, 30, 48, 183, 156, 35, 61, 26, 143, 74, 251, 94, 129, 162, 63, 152,
    170, 7, 115, 167, 241, 206, 3, 150, 55, 59, 151, 220, 90, 53, 23, 131,
    125, 173, 15, 238, 79, 95, 89, 16, 105, 137, 225, 224, 217, 160, 37, 123,
    118, 73, 2, 157, 46, 116, 9, 145, 134, 228, 207, 212, 202, 215, 69, 229,
    27, 188, 67, 124, 168, 252, 42, 4, 29, 108, 21, 247, 19, 205, 39, 203,
    233, 40, 186, 147, 198, 192, 155, 33, 164, 191, 98, 204, 165, 180, 117, 76,
    140, 36, 210, 172, 41, 54, 159, 8, 185, 232, 113, 196, 231, 47, 146, 120,
    51, 65, 28, 144, 254, 221, 93, 189, 194, 139, 112, 43, 71, 109, 184, 209,
  };
// s是需要進行索引的字符串指針,maxn是字符串可能的最大長度,返回值是相對地址。
inline int whashstr(char *s, int maxn)
  {
    register unsigned char oh, h;
    register unsigned char *p;
    register int i;
    if (!*s)
      return 0;
    p = (unsigned char *) s;
    oh = T[*p]; h = (*(p++) + 1) & 0xff;
    for (i = maxn - 1; *p && --i >= 0; )
    {
      oh = T[oh ^ *p]; h = T[h ^ *(p++)];
    }
    return (oh << 8) + h;
  }
  具體的算法就不說了,上面的那一大段東西不要問我為什么,這個算法的出處是CACM 33-6中的一個叫Peter K.Pearson的鬼子寫的論文中介紹的算法,據說速度非常的快。有了這個散列函數,我們就可以通過它來對世界里面的任意物件進行非常快的尋址了。
4.3、腳本語言解釋
  在設計腳本語言之前,我們首先需要明白,我們的腳本語言要實現什么樣的功能?否則隨心所欲的做下去寫出個C的解釋器之類的也說不定。我們要實現的功能只是簡單的邏輯判斷和循環,其他所有的功能都可以由事先提供好的函數來完成。嗯,這樣我們就可以列出一張工作量的表單:設計物件在底層的保存結構,提供腳本和底層間的訪問接口,設計支持邏輯判斷和循環的解釋器。
  下面先來談談物件在底層的保存結構。具體到每種不同屬性的物件,需要采用不同的結構,當然,如果你愿意的話,你可以所有的物件都采同同樣的結構,然后在結構里面設計一個散列表來保存各種不同的屬性。但這并不是一個好方法,過分的依賴散列表會讓你的游戲的邏輯變得繁雜不清。所以,盡量的區分每種不同的物件采用不同的結構來設計。但是有一點值得注意的是,不管是什么結構,有一些東西是統一的,就是我們所說的物件頭,那么我們怎么來設計這樣一個物件頭呢?
typedef struct object_head_s
  {
    char* name;
    char* prog;
  }object_head_t;
  其中name是在散列表中這個物件的索引號,prog則是腳本解釋器需要解釋的程序內容。下面我們就以NPC為例來設計一個結構:
typedef struct npc_s
  {
    object_head_t header; // 物件頭
int hp; // NPC的hp值。
int level; // NPC的等級。
struct position_s position; // 當前的位置信息。
unsigned int personality; // NPC的個性,一個unsigned int可以保存24種個性。
}npc_t;
  OK,結構設計完成,那么我們怎么來設計腳本解釋器呢?這里有兩種法,一種是用虛擬機的模式來解析腳本語言,另外一中則是用類似匯編語言的那種結構來設計,設置一些條件跳轉和循環就可以實現邏輯判斷和循環了,比如:
set name, "路人甲";
  CHOOSE: random_choose_personality; // 隨機選擇NPC的個性
compare hp, 100; // 比較氣血,比較出的值可以放在一個固定的變量里面
ifless LESS; // hp < 100的話,則返回。
jump CHOOSE; // 否則繼續選擇,只到選到一個hp < 100的。
 LESS: return success;
  這種腳本結構就類似CPU的指令的結構,一條一條指令按照順序執行,對于腳本程序員(Script Programmer)也可以培養他們匯編能力的說。
  那么怎么來模仿這種結構呢?我們拿CPU的指令做參照,首先得設置一些寄存器,CPU的寄存器的大小和數量是受硬件影響的,但我們是用內存來模擬寄存器,所以想要多大,就可以有多大。然后提供一些指令,包括四則運算,尋址,判斷,循環等等。接下來針對不同的腳本用不同的解析方法,比如說對NPC就用NPC固定的腳本,對ITEM就用ITEM固定的腳本,解析完以后就把結果生成底層該物件的結構用于使用。
  而如果要用虛擬機來實現腳本語言的話呢,則會將工程變得無比之巨大,強烈不推薦使用,不過如果你想做一個通用的網絡游戲底層的話,則可以考慮設計一個虛擬機。虛擬機大體的解釋過程就是進行兩次編譯,第一次對關鍵字進行編譯,第二次生成匯編語言,然后虛擬機在根據編譯生成的匯編語言進行逐行解釋,如果大家對這個感興趣的話,可以去www.mudos.org上下載一份MudOS的原碼來研究研究。 大體的思路講到這里已經差不多了,下面將用unreal(虛幻)為實例,談一談網絡游戲服務器的設計。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444971.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444971.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444971.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

leetcode45 跳躍游戲II 秒殺所有答案

給定一個非負整數數組&#xff0c;你最初位于數組的第一個位置。 數組中的每個元素代表你在該位置可以跳躍的最大長度。 你的目標是使用最少的跳躍次數到達數組的最后一個位置。 示例: 輸入: [2,3,1,1,4] 輸出: 2 解釋: 跳到最后一個位置的最小跳躍數是 2。 從下標為 …

MachineLearning(7)-決策樹基礎+sklearn.DecisionTreeClassifier簡單實踐

sklearn.DecisionTreeClassifier決策樹簡單使用1.決策樹算法基礎2.sklearn.DecisionTreeClassifier簡單實踐2.1 決策樹類2.3 決策樹構建2.3.1全數據集擬合&#xff0c;決策樹可視化2.3.2交叉驗證實驗2.3.3超參數搜索2.3.4模型保存與導入2.3.5固定隨機數種子參考資料1.決策樹算法…

游戲服務器體系結構

本文描述了一個我所設計的游戲服務器體系結構,其目的是實現游戲服務器的動態負載平衡,將對象從繁忙的服務器轉移到相對空閑的服務器中.設計并沒有經過具體的測試與驗證,僅僅是將自己目前的一些想法記錄下來.隨著新構思的出現,可能會有所變化. 以下是服務器的邏輯視圖,其中忽略…

游戲服務器架構探討

要描述一項技術或是一個行業&#xff0c;一般都會從其最古老的歷史開始說起&#xff0c;我本也想按著這個套路走&#xff0c;無奈本人乃一八零后小輩&#xff0c;沒有經歷過那些苦澀的卻令人羨慕的單機游戲開發&#xff0c;也沒有響當當的拿的出手的優秀作品&#xff0c;所以也…

leetcode72 編輯距離

給定兩個單詞 word1 和 word2&#xff0c;計算出將 word1 轉換成 word2 所使用的最少操作數 。 你可以對一個單詞進行如下三種操作&#xff1a; 插入一個字符 刪除一個字符 替換一個字符 示例 1: 輸入: word1 "horse", word2 "ros" 輸出: 3 解釋: ho…

即時通訊系統架構

有過幾款IM系統開發經歷&#xff0c;目前有一款還在線上跑著。準備簡單地介紹一下大型商業應用的IM系統的架構。設計這種架構比較重要的一點是低耦合&#xff0c;把整個系統設計成多個相互分離的子系統。我把整個系統分成下面幾個部分&#xff1a;&#xff08;1&#xff09;狀態…

leetcode303 區域和檢索

給定一個整數數組 nums&#xff0c;求出數組從索引 i 到 j (i ≤ j) 范圍內元素的總和&#xff0c;包含 i, j 兩點。 示例&#xff1a; 給定 nums [-2, 0, 3, -5, 2, -1]&#xff0c;求和函數為 sumRange() sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0,…

算法(24)-股票買賣

股票買賣1.動態規劃框架LeetCode-121 一次買賣LeetCode-122 不限次數LeetCode-309 不限次數冷凍期LeetCode-714 不限次數手續費LeetCode-123 兩次買賣LeetCode-188 k次買賣2.貪心特解LeetCode-121 一次買賣LeetCode-122 不限次數解題思路參考buladong解題&#xff0c;詳細信息可…

網絡游戲的客戶端同步問題 .

有關位置同步的方案實際上已經比較成熟&#xff0c;網上也有比較多的資料可供參考。在《帶寬限制下的視覺實體屬性傳播》一文中&#xff0c;作者也簡單提到了位置同步方案的構造過程&#xff0c;但涉及到細節的地方沒有深入&#xff0c;這里專門針對這一主題做些回顧。 最直接的…

leetcode319 燈泡的開關

初始時有 n 個燈泡關閉。 第 1 輪&#xff0c;你打開所有的燈泡。 第 2 輪&#xff0c;每兩個燈泡你關閉一次。 第 3 輪&#xff0c;每三個燈泡切換一次開關&#xff08;如果關閉則開啟&#xff0c;如果開啟則關閉&#xff09;。第 i 輪&#xff0c;每 i 個燈泡切換一次開關。 …

網游服務器端設計思考:心跳設計

網絡游戲服務器的主要作用是模擬整個游戲世界&#xff0c;客戶端用過網絡連接把一些信息數據發給服務器&#xff0c;在操作合法的情況下&#xff0c;更新服務器上該客戶端對應的player實體、所在場景等&#xff0c;并把這些操作及其影響廣播出去。讓別的客戶端能顯示這些操作。…

算法(25)-括號

各種括號1.LeetCode-22 括號生成--各種括號排列組合2.LeetCode-20 有效括號(是否)--堆棧3.LeetCode-32 最長有效括號(長度)--dp4.LeetCode-301刪除無效括號 --多種刪除方式1.LeetCode-22 括號生成–各種括號排列組合 數字 n 代表生成括號的對數&#xff0c;請你設計一個函數&a…

(二十)深入淺出TCPIP之epoll的一些思考

Epoll基本介紹 在linux的網絡編程中,很長的時間都在使用select來做事件觸發。在linux新的內核中,有了一種替換它的機制,就是epoll。相比于 select,epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select實現中,它是采用輪詢來處理的,輪詢的fd…

leetcode542 01矩陣

給定一個由 0 和 1 組成的矩陣&#xff0c;找出每個元素到最近的 0 的距離。 兩個相鄰元素間的距離為 1 。 示例 1: 輸入: 0 0 0 0 1 0 0 0 0 輸出: 0 0 0 0 1 0 0 0 0 示例 2: 輸入: 0 0 0 0 1 0 1 1 1 輸出: 0 0 0 0 1 0 1 2 1 注意: 給定矩陣的元素個數不超過 10000。…

RPC、RMI與MOM與組播 通信原理 .

遠程過程調用&#xff08;RPC&#xff09;&#xff1a; 即對遠程站點機上的過程進行調用。當站點機A上的一個進程調用另一個站點機上的過程時&#xff0c;A上的調用進程掛起&#xff0c;B上的被調用過程執行&#xff0c;并將結果返回給調用進程&#xff0c;使調用進程繼續執行【…

網關服務器 .

之前想著要把什么什么給寫一下&#xff0c;每次都太懶了&#xff0c;都是想起了才來寫一下。今天只討論游戲服務器的網關服務器。 1.轉發 轉發客戶端和服務器間的消息&#xff0c;網關將場景、會話、數據、名字、平臺等服務器的數據轉發給客戶端&#xff0c;接收客戶端的數據&a…

算法(26)-最長系列

最長系列1.LeetCode-32 最長有效括號--子串2.LeetCode-300 最長上升子序列--長度3.LeetCode-32 最長回文子串--是什么5.LeetCode-512 最長回文子序列--長度6.LeetCode-1143 最長公共子序列--長度6.LeetCode-128 最長連續序列--長度7.LeetCode-14 最長公共前綴-字符串8.劍指offe…

一個簡單的游戲服務器框架 .

最近一段時間不是很忙&#xff0c;就寫了一個自己的游戲服務器框架雛形&#xff0c;很多地方還不夠完善&#xff0c;但是基本上也算是能夠跑起來了。我先從上層結構說起&#xff0c;一直到實現細節吧&#xff0c;想起什么就寫什么。 第一部分 服務器邏輯 服務器這邊簡單的分為三…

游戲登陸流程 .

當公司有很多游戲的時候&#xff0c;那么公司往往會有一個統一的賬號管理平臺&#xff0c;就就像盛大通行證、網易通行證&#xff0c;戰網平臺&#xff0c;這些平臺統一管理游戲的賬號數據。 打個比方&#xff0c;現在我們玩星辰變&#xff0c;那么玩家登陸游戲的時候…

leetcode97 交錯字符串

給定三個字符串 s1, s2, s3, 驗證 s3 是否是由 s1 和 s2 交錯組成的。 示例 1: 輸入: s1 "aabcc", s2 "dbbca", s3 "aadbbcbcac" 輸出: true 示例 2: 輸入: s1 "aabcc", s2 "dbbca", s3 "aadbbbaccc" 輸…