小談Online-game服務器端設計(1、2)

談這個話題之前,首先要讓大家知道,什么是服務器。在網絡游戲中,服務器所扮演的角色是同步,廣播和服務器主動的一些行為,比如說天氣,NPC AI之類的,之所以現在的很多網絡游戲服務器都需要負擔一些游戲邏輯上的運算是因為為了防止客戶端的作弊行為。了解到這一點,那么本系列的文章將分為兩部分來談談網絡游戲服務器的設計,一部分是講如何做好服務器的網絡連接,同步,廣播以及NPC的設置,另一部分則將著重談談哪些邏輯放在服務器比較合適,并且用什么樣的結構來安排這些邏輯。
服務器的網絡連接
  大多數的網絡游戲的服務器都會選擇非阻塞select這種結構,為什么呢?因為網絡游戲的服務器需要處理的連接非常之多,并且大部分會選擇在Linux/Unix下運行,那么為每個用戶開一個線程實際上是很不劃算的,一方面因為在Linux/Unix下的線程是用進程這么一個概念模擬出來的,比較消耗系統資源,另外除了I/O之外,每個線程基本上沒有什么多余的需要并行的任務,而且網絡游戲是互交性非常強的,所以線程間的同步會成為很麻煩的問題。由此一來,對于這種含有大量網絡連接的單線程服務器,用阻塞顯然是不現實的。對于網絡連接,需要用一個結構來儲存,其中需要包含一個向客戶端寫消息的緩沖,還需要一個從客戶端讀消息的緩沖,具體的大小根據具體的消息結構來定了。另外對于同步,需要一些時間校對的值,還需要一些各種不同的值來記錄當前狀態,下面給出一個初步的連接的結構:
typedef connection_s {
??? user_t *ob; /* 指向處理服務器端邏輯的結構 */
??? int fd; /* socket連接 */
??? struct sockaddr_in addr; /* 連接的地址信息 */
??? char text[MAX_TEXT]; /* 接收的消息緩沖 */
??? int text_end; /* 接收消息緩沖的尾指針 */
??? int text_start; /* 接收消息緩沖的頭指針 */
??? int last_time; /* 上一條消息是什么時候接收到的 */
??? struct timeval latency; /* 客戶端本地時間和服務器本地時間的差值 */
??? struct timeval last_confirm_time; /* 上一次驗證的時間 */
??? short is_confirmed; /* 該連接是否通過驗證過 */
??? int ping_num; /* 該客戶端到服務器端的ping值 */
??? int ping_ticker; /* 多少個IO周期處理更新一次ping值 */
??? int message_length; /* 發送緩沖消息長度 */
??? char message_buf[MAX_TEXT]; /* 發送緩沖區 */
??? int iflags; /* 該連接的狀態 */
} connection_t;
  服務器循環的處理所有連接,是一個死循環過程,每次循環都用select檢查是否有新連接到達,然后循環所有連接,看哪個連接可以寫或者可以讀,就處理該連接的讀寫。由于所有的處理都是非阻塞的,所以所有的Socket IO都可以用一個線程來完成。
  由于網絡傳輸的關系,每次recv()到的數據可能不止包含一條消息,或者不到一條消息,那么怎么處理呢?所以對于接收消息緩沖用了兩個指針,每次接收都從text_start開始讀起,因為里面殘留的可能是上次接收到的多余的半條消息,然后text_end指向消息緩沖的結尾。這樣用兩個指針就可以很方便的處理這種情況,另外有一點值得注意的是:解析消息的過程是一個循環的過程,可能一次接收到兩條以上的消息在消息緩沖里面,這個時候就應該執行到消息緩沖里面只有一條都不到的消息為止,大體流程如下:
while ( text_end – text_start > 一條完整的消息長度 )
{
??? 從text_start處開始處理;
??? text_start += 該消息長度;
}
memcpy ( text, text + text_start, text_end – text_start );
  對于消息的處理,這里首先就需要知道你的游戲總共有哪些消息,所有的消息都有哪些,才能設計出比較合理的消息頭。一般來說,消息大概可分為主角消息,場景消息,同步消息和界面消息四個部分。其中主角消息包括客戶端所控制的角色的所有動作,包括走路,跑步,戰斗之類的。場景消息包括天氣變化,一定的時間在場景里出現一些東西等等之類的,這類消息的特點是所有消息的發起者都是服務器,廣播對象則是場景里的所有玩家。而同步消息則是針對發起對象是某個玩家,經過服務器廣播給所有看得見他的玩家,該消息也是包括所有的動作,和主角消息不同的是該種消息是服務器廣播給客戶端的,而主角消息一般是客戶端主動發給服務器的。最后是界面消息,界面消息包括是服務器發給客戶端的聊天消息和各種屬性及狀態信息。
  下面來談談消息的組成。一般來說,一個消息由消息頭和消息體兩部分組成,其中消息頭的長度是不變的,而消息體的長度是可變的,在消息體中需要保存消息體的長度。由于要給每條消息一個很明顯的區分,所以需要定義一個消息頭特有的標志,然后需要消息的類型以及消息ID。消息頭大體結構如下:
type struct message_s {
??? unsigned short message_sign;
??? unsigned char message_type;
??? unsigned short message_id
??? unsigned char message_len
}message_t;
服務器的廣播
  服務器的廣播的重點就在于如何計算出廣播的對象。很顯然,在一張很大的地圖里面,某個玩家在最東邊的一個動作,一個在最西邊的玩家是應該看不到的,那么怎么來計算廣播的對象呢?最簡單的辦法,就是把地圖分塊,分成大小合適的小塊,然后每次只象周圍幾個小塊的玩家進行廣播。那么究竟切到多大比較合適呢?一般來說,切得塊大了,內存的消耗會增大,切得塊小了,CPU的消耗會增大(原因會在后面提到)。個人覺得切成一屏左右的小塊比較合適,每次廣播廣播周圍九個小塊的玩家,由于廣播的操作非常頻繁,那么遍利周圍九塊的操作就會變得相當的頻繁,所以如果塊分得小了,那么遍利的范圍就會擴大,CPU的資源會很快的被吃完。
  切好塊以后,怎么讓玩家在各個塊之間走來走去呢?讓我們來想想在切換一次塊的時候要做哪些工作。首先,要算出下個塊的周圍九塊的玩家有哪些是現在當前塊沒有的,把自己的信息廣播給那些玩家,同時也要算出下個塊周圍九塊里面有哪些物件是現在沒有的,把那些物件的信息廣播給自己,然后把下個塊的周圍九快里沒有的,而現在的塊周圍九塊里面有的物件的消失信息廣播給自己,同時也把自己消失的消息廣播給那些物件。這個操作不僅煩瑣而且會吃掉不少CPU資源,那么有什么辦法可以很快的算出這些物件呢?一個個做比較?顯然看起來就不是個好辦法,這里可以參照二維矩陣碰撞檢測的一些思路,以自己周圍九塊為一個矩陣,目標塊周圍九塊為另一個矩陣,檢測這兩個矩陣是否碰撞,如果兩個矩陣相交,那么沒相交的那些塊怎么算。這里可以把相交的塊的坐標轉換成內部坐標,然后再進行運算。
  對于廣播還有另外一種解決方法,實施起來不如切塊來的簡單,這種方法需要客戶端來協助進行運算。首先在服務器端的連接結構里面需要增加一個廣播對象的隊列,該隊列在客戶端登陸服務器的時候由服務器傳給客戶端,然后客戶端自己來維護這個隊列,當有人走出客戶端視野的時候,由客戶端主動要求服務器給那個物件發送消失的消息。而對于有人總進視野的情況,則比較麻煩了。
  首先需要客戶端在每次給服務器發送update position的消息的時候,服務器都給該連接算出一個視野范圍,然后在需要廣播的時候,循環整張地圖上的玩家,找到坐標在其視野范圍內的玩家。使用這種方法的好處在于不存在轉換塊的時候需要一次性廣播大量的消息,缺點就是在計算廣播對象的時候需要遍歷整個地圖上的玩家,如果當一個地圖上的玩家多得比較離譜的時候,該操作就會比較的慢。
服務器的同步
  同步在網絡游戲中是非常重要的,它保證了每個玩家在屏幕上看到的東西大體是一樣的。其實呢,解決同步問題的最簡單的方法就是把每個玩家的動作都向其他玩家廣播一遍,這里其實就存在兩個問題:1,向哪些玩家廣播,廣播哪些消息。2,如果網絡延遲怎么辦。事實上呢,第一個問題是個非常簡單的問題,不過之所以我提出這個問題來,是提醒大家在設計自己的消息結構的時候,需要把這個因素考慮進去。而對于第二個問題,則是一個挺麻煩的問題,大家可以來看這么個例子:
  比如有一個玩家A向服務器發了條指令,說我現在在P1點,要去P2點。指令發出的時間是T0,服務器收到指令的時間是T1,然后向周圍的玩家廣播這條消息,消息的內容是“玩家A從P1到P2”有一個在A附近的玩家B,收到服務器的這則廣播的消息的時間是T2,然后開始在客戶端上畫圖,A從P1到P2點。這個時候就存在一個不同步的問題,玩家A和玩家B的屏幕上顯示的畫面相差了T2-T1的時間。這個時候怎么辦呢?
  有個解決方案,我給它取名叫 預測拉扯,雖然有些怪異了點,不過基本上大家也能從字面上來理解它的意思。要解決這個問題,首先要定義一個值叫:預測誤差。然后需要在服務器端每個玩家連接的類里面加一項屬性,叫latency,然后在玩家登陸的時候,對客戶端的時間和服務器的時間進行比較,得出來的差值保存在latency里面。還是上面的那個例子,服務器廣播消息的時候,就根據要廣播對象的latency,計算出一個客戶端的CurrentTime,然后在消息頭里面包含這個CurrentTime,然后再進行廣播。并且同時在玩家A的客戶端本地建立一個隊列,保存該條消息,只到獲得服務器驗證就從未被驗證的消息隊列里面將該消息刪除,如果驗證失敗,則會被拉扯回P1點。然后當玩家B收到了服務器發過來的消息“玩家A從P1到P2”這個時候就檢查消息里面服務器發出的時間和本地時間做比較,如果大于定義的預測誤差,就算出在T2這個時間,玩家A的屏幕上走到的地點P3,然后把玩家B屏幕上的玩家A直接拉扯到P3,再繼續走下去,這樣就能保證同步。更進一步,為了保證客戶端運行起來更加smooth,我并不推薦直接把玩家拉扯過去,而是算出P3偏后的一點P4,然后用(P4-P1)/T(P4-P3)來算出一個很快的速度S,然后讓玩家A用速度S快速移動到P4,這樣的處理方法是比較合理的,這種解決方案的原形在國際上被稱為(Full plesiochronous),當然,該原形被我篡改了很多來適應網絡游戲的同步,所以而變成所謂的:預測拉扯。
  另外一個解決方案,我給它取名叫 驗證同步,聽名字也知道,大體的意思就是每條指令在經過服務器驗證通過了以后再執行動作。具體的思路如下:首先也需要在每個玩家連接類型里面定義一個latency,然后在客戶端響應玩家鼠標行走的同時,客戶端并不會先行走動,而是發一條走路的指令給服務器,然后等待服務器的驗證。服務器接受到這條消息以后,進行邏輯層的驗證,然后計算出需要廣播的范圍,包括玩家A在內,根據各個客戶端不同的latency生成不同的消息頭,開始廣播,這個時候這個玩家的走路信息就是完全同步的了。這個方法的優點是能保證各個客戶端之間絕對的同步,缺點是當網絡延遲比較大的時候,玩家的客戶端的行為會變得比較不流暢,給玩家帶來很不爽的感覺。該種解決方案的原形在國際上被稱為(Hierarchical master-slave synchronization),80年代以后被廣泛應用于網絡的各個領域。
  最后一種解決方案是一種理想化的解決方案,在國際上被稱為Mutual synchronization,是一種對未來網絡的前景的良好預測出來的解決方案。這里之所以要提這個方案,并不是說我們已經完全的實現了這種方案,而只是在網絡游戲領域的某些方面應用到這種方案的某些思想。我對該種方案取名為:半服務器同步。大體的設計思路如下:
  首先客戶端需要在登陸世界的時候建立很多張廣播列表,這些列表在客戶端后臺和服務器要進行不及時同步,之所以要建立多張列表,是因為要廣播的類型是不止一種的,比如說有local message,有remote message,還有global message 等等,這些列表都需要在客戶端登陸的時候根據服務器發過來的消息建立好。在建立列表的同時,還需要獲得每個列表中廣播對象的latency,并且要維護一張完整的用戶狀態列表在后臺,也是不及時的和服務器進行同步,根據本地的用戶狀態表,可以做到一部分決策由客戶端自己來決定,當客戶端發送這部分決策的時候,則直接將最終決策發送到各個廣播列表里面的客戶端,并對其時間進行校對,保證每個客戶端在收到的消息的時間是和根據本地時間進行校對過的。那么再采用預測拉扯中提到過的計算提前量,提高速度行走過去的方法,將會使同步變得非常的smooth。該方案的優點是不通過服務器,客戶端自己之間進行同步,大大的降低了由于網絡延遲而帶來的誤差,并且由于大部分決策都可以由客戶端來做,也大大的降低了服務器的資源。由此帶來的弊端就是由于消息和決策權都放在客戶端本地,所以給外掛提供了很大的可乘之機。

?

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

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

相關文章

Linux(8)-Linux下的編程開發-C/C++、PHP、JAVA概述

Linux下的編程開發1.C/C語言開發環境的搭建2.PHP開發環境搭建3.JAVA開發環境搭建1.C/C語言開發環境的搭建 方式1:文本編輯器編譯器(gcc/g) Ubuntu 下常用的文本編輯器: Gedit–語法高亮Vim–vi(無比強大無比難用)的改進。字符界面/圖形界面…

leetcode55 跳躍游戲 秒殺所有答案

給定一個非負整數數組,你最初位于數組的第一個位置。 數組中的每個元素代表你在該位置可以跳躍的最大長度。 判斷你是否能夠到達最后一個位置。 示例 1: 輸入: [2,3,1,1,4] 輸出: true 解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然后再從位置 …

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

下面我想來談談關于服務器上NPC的設計以及NPC智能等一些方面涉及到的問題。首先,我們需要知道什么是NPC,NPC需要做什么。NPC的全稱是(Non-Player Character),很顯然,他是一個character,但不是玩…

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

在這一章節,我想談談關于服務器端的腳本的相關設計。因為在上一章節里面,談NPC智能相關的時候已經接觸到一些腳本相關的東東了。還是先來談談腳本的作用吧。   在基于編譯的服務器端程序中,是無法在程序的運行過程中構建一些東西的&#xf…

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

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

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

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

游戲服務器體系結構

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

游戲服務器架構探討

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

leetcode72 編輯距離

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

即時通訊系統架構

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

leetcode303 區域和檢索

給定一個整數數組 nums,求出數組從索引 i 到 j (i ≤ j) 范圍內元素的總和,包含 i, j 兩點。 示例: 給定 nums [-2, 0, 3, -5, 2, -1],求和函數為 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解題,詳細信息可…

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

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

leetcode319 燈泡的開關

初始時有 n 個燈泡關閉。 第 1 輪,你打開所有的燈泡。 第 2 輪,每兩個燈泡你關閉一次。 第 3 輪,每三個燈泡切換一次開關(如果關閉則開啟,如果開啟則關閉)。第 i 輪,每 i 個燈泡切換一次開關。 …

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

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

算法(25)-括號

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

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

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

leetcode542 01矩陣

給定一個由 0 和 1 組成的矩陣,找出每個元素到最近的 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與組播 通信原理 .

遠程過程調用(RPC): 即對遠程站點機上的過程進行調用。當站點機A上的一個進程調用另一個站點機上的過程時,A上的調用進程掛起,B上的被調用過程執行,并將結果返回給調用進程,使調用進程繼續執行【…

網關服務器 .

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