MMORPG服務器架構

一.摘要

html

1.網絡遊戲MMORPG總體服務器框架,包括早期,中期,當前的一些主流架構
2.網絡遊戲網絡層,包括網絡協議,IO模型,網絡框架,消息編碼等。
3.網絡遊戲的場景管理,AI,腳本的應用等。
4.開源的網絡服務器引擎
5.參考書籍,博客

二.關鍵詞

java

網絡協議 網絡IO 消息 廣播 同步 CS TCP/UDP IP 集羣 負載均衡 分佈式 
網關服務器 GateServer 心跳 多線程/線程池 開源網絡通信框架/模型
阻塞/非阻塞/同步/異步    Proactor/Reactor/Actor Select/Poll/Epoll/Iocp/Kqueue 
遊戲開發中的設計模式/數據結構
短鏈接和長鏈接 遊戲安全 緩存 消息編碼協議 腳本語言 
Socket Nagle/粘包/截斷/TCP_NODELAY AI/場景 分線/分地圖 開源MMORPG服務器

三.正文框架結構

1.    早期的MMORPG服務器結構

Client<->GameServer<->DB    全部業務,數據集中處理

優勢:簡單,快速開發
缺點:
    1.全部業務放在一塊兒,系統負擔大大增長.一個bug可能致使整個服務器崩潰,形成全部玩家掉線甚至丟失等嚴重後果。
    2.開服一剎那,全部玩家所有堆積在同一個新手村.->>>>卡,客戶端卡(同屏人數過多渲染/廣播風暴) 服務器卡(處理大量同場景消息/廣播風暴)
2.    中期-用戶分離集羣式

                GameServe1
Client            |                    DB
                GameServer2

玩家不斷增多->分線->程序自動或玩家手動選擇進入
缺點:運營到後期,隨着每條線玩家的減小, 互動大大減小。

3.    中後期 數據分離集羣式
按地圖劃分服務器,當前主流
    新手村問題:《天龍八部》提出了較好的解決方案,創建多個平行的新手村地圖,一主多副,開服時儘量多的同時容納新用戶的涌入,高等級玩家從其它地圖回新手村只能到達主新手村。

4.    當前主流的網絡遊戲架構


        注:在GateServer和CenterServer之間是有一條TCP鏈接的。而GameServer和LogServer之間的鏈接能夠是UDP鏈接。這是有一個大概的圖,不少地方須要細化。
GateServer:網關服務器,AgentServer、ProxyServer

 優勢: 
    (1)做爲網絡通訊的中轉站,負責維護將內網和外網隔離開,使外部沒法直接訪問內部服務器,保障內網服務器的安全,必定程度上較少外掛的攻擊。
    (2)網關服務器負責解析數據包、加解密、超時處理和必定邏輯處理,這樣能夠提早過濾掉錯誤包和非法數據包。
    (3)客戶端程序只需創建與網關服務器的鏈接便可進入遊戲,無需與其它遊戲服務器同時創建多條鏈接,節省了客戶端和服務器程序的網絡資源開銷。
    (4)在玩家跳服務器時,不須要斷開與網關服務器的鏈接,玩家數據在不一樣遊戲服務器間的切換是內網切換,切換工做瞬問完成,玩家幾乎察覺不到,這保證了遊戲的流暢性和良好的用戶體驗。

   缺點: 
1.網關服務器成爲高負載狀況下的通信瓶頸問題
2因爲網關的單節點故障致使整組服務器沒法對外提供服務的問題

   解決:多網關技術。顧名思義,「多網關」 就是同時存在多個網關服務器,好比一組服務器能夠配置三臺GameGme。當負載較大時,能夠經過增長網關服務器來增長網關的整體通信流量,當一臺網關服務器宕機時,它只會影響鏈接到本服務器的客戶端,其它客戶端不會受到任何影響。

DCServer:數據中心服務器。主要的功能是緩存玩家角色數據,保證角色數據能快速的讀取和保存
CenterServer:全局服務器/中心服務器,也叫WorldServer. 主要負責維持GameServer之間數據的轉發和數據廣播。另一些遊戲系統也可能會放到Center上處理,好比好友系統,公會系統。

    改進:將網關服務器細化爲LogingateServer和多個GameGateServer.

5.    按業務分離式集羣
因爲網絡遊戲存在不少的業務,如聊天,戰鬥,行走,NPC等,能夠將某些業務分到單獨的服務器上。這樣每一個服務器的程序則會精簡不少。並且一些大流量業務的分離,能夠有效的提升遊戲服務器人數上限。

 

優勢:
      1.業務的分離使得每種服務器的程序變的簡單,這樣能夠下降出錯的概率。即便出錯,也不至於影響到每個整個遊戲的進行,並且經過快速啓動另外一臺備用服務器替換出錯的服務器。
     2.業務的分離使得流量獲得了分散,進而相應速度回獲得提高 。
     3.大部分業務都分離了成了單獨的服務器,因此能夠動態的添加,從而提升人數上限。

改進:甚至能夠將登錄服務器細化拆分建角色,選擇角色服務器

6.    一種簡單實用的網絡遊戲服務器架構

下圖中每一個方框表示一個獨立的進程APP組件,每一個服務進程若是發生宕機會影響部分用戶,總體服務但不會所有中斷。在宕機進程重啓後,又能夠併入總體,所有服務得以繼續。



gls:game login server,遊戲登陸服務器,某種程序上,其不是核心組件,gls調用外部的接口,進行基本的用戶名密碼認證。此外須要實現不少附屬的功能:登陸排隊(對開服很是有幫助),GM超級登陸通道(GM能夠不排隊進入遊戲),封測期間激活用戶控制,限制用戶登陸,控制客戶端版本等。
db:實質上是後臺sql的大內存緩衝,隔離了數據庫操做,比較內存中的數據,只把改變的數據定時批量寫入sql。系統的算法,開發穩定性都要求很是高。
center:全部組件都要在這裏註冊,在線玩家的session狀態都在這裏集中存放,和各組件有心跳鏈接。全部對外的接口也所有經過這裏。
角色入口:玩家登陸游戲後的選擇角色
gs:game server,最核心組件,同一地圖,全部遊戲邏輯相關的功能,都在這裏完成。
gate:創建和用戶的常連接,主要做sockt轉發,屏蔽惡意包,對gs進行保護。協議加密解密功能,一個gate共享多個gs,下降跳轉地圖鏈接不上的風險。
IM,關係,寄售:表示其它組件,負責對應的跨地圖發生全局的遊戲邏輯。

7.另外一個架構圖


    1-   這是一條WebService的管道,在用戶激活該區賬號,或者修改賬號密碼的時候,經過這條通道來插入和更新用戶的賬號信息。
    2-   這也是一條WebService管道,用來獲取和控制用戶該該組內的角色信息,以及進行付費商城代幣之類的更新操做。
    3-   這是一條本地的TCP/IP鏈接,這條鏈接主要用來進行服務器組在登錄服務器的註冊,以及登錄服務器驗證賬戶後,向用戶服務器註冊賬戶登錄信息,以及進行對已經登錄的賬戶角色信息進行操做(好比踢掉當前登錄的角色),還有服務器組的信息更新(當前在線玩家數量等)。
    4-   這也是一條本地TCP/IP鏈接,這條鏈接用來對鏈接到GameServer的客戶端進行驗證,以及獲取角色數據信息,還有傳回GameServer上角色的數據信息改變。
    5-   這條鏈接也是一條本地的TCP/IP鏈接,它用來進行公共信息服務器和數個遊戲服務器間的交互,用來交換一些遊戲世界級的信息(好比公會信息,跨服組隊信息,跨服聊天頻道等)。
    6-   這裏的兩條鏈接,想表達的意思是,UserServer和GameServer的Agent是能夠互換使用的,也就是玩家進入組內以後,就不須要再切換Agent。若是不怕亂套,也能夠把登錄服務器的Agent也算上,這樣用戶整個過程裏就不須要再更換Agent,減小重複鏈接的次數,也提升了穩定性。(畢竟鏈接次數少了,也下降了連不上服務器的出現概率)
在這個架構裏面,GameServer其實是一個遊戲邏輯的綜合體,裏面能夠再去擴展成幾個不一樣的邏輯服務器,經過PublicServer進行公共數據交換。
    UserServer實際上扮演了一個ServerGroup的領頭羊的角色,它負責向LoginServer註冊和更新服務器組的信息(名字,當前人數),而且對Agent進行調度,對選擇了該組的玩家提供一個用戶量最少的Agent。同時,它也兼了一個角色管理服務器的功能,發送給客戶端當前的角色列表,角色的建立,刪除,選擇等管理操做,都是在這裏進行的。並且,它仍是一個用戶信息的驗證服務器,GameServer須要經過它來進行客戶端的合法性驗證,以及獲取玩家選擇的角色數據信息。
採用這種架構的遊戲,一般有如下表現。
    1- 用戶必須激活一個大區,才能在大區內登錄本身的賬號。
    2- 用戶啓動客戶端的時候,彈出一個登錄器,選擇大區。
    3- 用戶啓動真正的客戶端的時候,一開始就是輸入賬號密碼。
    4- 賬號驗證完成以後,進行區內的服務器選擇。
    5- 服務器選擇完成以後,進入角色管理。同時,角色在不一樣的服務器裏不能共享。
四.正文網絡通信

1.網絡協議
 根據遊戲類型    實時性要求/是否容許丟包 來決定 TCP/UDP協議

a.TCP:面向鏈接,可靠,保證順序,慢,有延遲
     TCP每次發送一個數據包後都要等待接收方發送一個應答信息,這樣TCP才能夠確認數據包經過因特網完整地送到了接收方。若是在一段時間內TCP沒有收到接收方的應答,他就會中止發送新的數據包,轉而去從新發送沒有收到應答2的數據包,而且持續這種發送狀態知道收到接收方的應答。因此這會形成網絡數據傳輸的延遲,若網絡狀況很差,發送方會等待至關長一段時間
       UDP:無鏈接,不可靠,不保證順序,快

b.長鏈接/短鏈接
長鏈接,指在一個TCP鏈接上能夠連續發送多個數據包,在TCP鏈接保持期間,若是沒有數據包發送,須要雙方發檢測包以維持此鏈接,通常須要本身作在線維
    鏈接→數據傳輸→保持鏈接(心跳)→數據傳輸→保持鏈接(心跳)→……→關閉鏈接
短鏈接是指通訊雙方有數據交互時,就創建一個TCP鏈接,數據發送完成後,則斷開此TCP鏈接,如Http
    鏈接→數據傳輸→關閉鏈接

2.IO模型

       Unix5中io模型
1.    阻塞IO (Blocking I/O Model)
2.    非阻塞IO (Nonblocking I/O Model)
3.    IO複用 (I/O Multiplexing Model)
4.    信號驅動IO (Signal-Driven I/O Model)
5.    異步IO (Asynchronous I/O Model)

IO分兩個階段:
1.通知內核準備數據。2.數據從內核緩衝區拷貝到應用緩衝區

根據這2點IO類型能夠分紅:
    1.阻塞IO,在兩個階段上面都是阻塞的。
    2.非阻塞IO,在第1階段,程序不斷的輪詢直到數據準備好,第2階段仍是阻塞的
    3.IO複用,在第1階段,當一個或者多個IO準備就緒時,通知程序,第2階段仍是阻塞的,在第1階段仍是輪詢實現的,只是全部的IO都集中在一個地方,這個地方進行輪詢
    4.信號IO,當數據準備完畢的時候,信號通知程序數據準備完畢,第2階段阻塞
    5.異步IO,1,2都不阻塞







   
同時阻塞多個I/O操做。並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操做函數
Java#Selector

   

容許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,能夠在信號處理函數中調用I/O操做函數處理數據.算法

 

 

Java#NIO2
發出系統調用後,直接返回。通知IO操做完成。
前四種同步IO,最後一種異步IO.兩者區別:第二個階段必需要求進程主動調用recvfrom.而異步io則將io操做所有交給內核完成,完成後發信號通知。此期間,用戶不須要去檢查IO操做的狀態,也不須要主動的去拷貝數據。

3.線程阻塞的緣由:

    1.Thread.sleep(),線程放棄CPU,睡眠N秒,而後恢復運行
    2.線程要執行一段同步代碼,因爲沒法得到相關的鎖,阻塞。得到同步鎖後,才能夠恢復運行。
    .線程執行了一個對象的wait方法,進入阻塞狀態,只有等到其餘線程執行了該對象的notify、nnotifyAll,才能將其喚醒。
    4.IO操做,等待相關資源
阻塞線程的共同特色是:放棄CPU,中止運行,只有等到致使阻塞的緣由消除,才能恢復運行 。或者被其餘線程中斷,該線程會退出阻塞狀態,並拋出InterruptedException.

4.
阻塞/非阻塞/同步/異步
同步/異步關注的是消息如何通知的機制。而阻塞和非阻塞關注的是處理消息。是兩組徹底不一樣的概念。

5.幾個經常使用概念
Select Poll
Epoll(Linux) Kqueue(FreeBSD)   
IOCP    windows
 
Reactor
Dispatcher(分發器),Notifer(通知器), 事件到來時,使用Dispatcher(分發器)對Handler進行分派,這個Dispatcher要對全部註冊的Handler進行維護。同時,有一個Demultiplexer(分揀器)對多路的同步事件進行分揀。    

Proactor
Proactor和Reactor都是併發編程中的設計模式.用於派發/分離IO操做事件的。這裏所謂的IO事件也就是諸如read/write的IO操做。"派發/分離"就是將單獨的IO事件通知到上層模塊。兩個模式不一樣的地方在於,Proactor用於異步IO,而Reactor用於同步IO。

兩個模式的相同點,都是對某個IO事件的事件通知(即告訴某個模塊,這個IO操做能夠進行或已經完成)。在結構上,二者也有相同點:demultiplexor負責提交IO操做(異步)、查詢設備是否可操做(同步),而後當條件知足時,就回調handler。
不一樣點在於,異步狀況下(Proactor),當回調handler時,表示IO操做已經完成;同步狀況下(Reactor),回調handler時,表示IO設備能夠進行某個操做(can read or can write),handler這個時候開始提交操做。

6.
網絡通信框架
TCP Server框架:
Apache MINA(Multipurpose Infrastructure for Network Applications)2.0.4
Netty 3.5.0Final
Grizzly 2.2
Quickserver是一個免費的開源Java庫,用於快速建立健壯的多線程、多客戶端TCP服務器應用程序。使用QuickServer,用戶能夠只集中處理應用程序的邏輯/協議
Cindy 強壯,可擴展,高效的異步I/O框架
xSocket一個輕量級的基於nio的服務器框架用於開發高性能、可擴展、多線程的服務器。該框架封裝了線程處理、異步讀/寫等方面
ACE 6.1.0 C++ADAPTIVE CommunicationEnvironment,
SmaxFoxServer 2.X 專門爲Adobe Flash設計的跨平臺socket服務器

7.消息編碼協議
AMF/JSON/XML/自定義/ProtocolBuffer

不管是作何種網絡應用,必需要解決的問題之一就是應用層從字節流中拆分出消息的問題,也就是對於 TCP 這種字節流協議,接收方應用層可以從字節流中識別發送方傳輸的消息.
1.使用特殊字符或者字符串做爲消息的邊界,應用層解析收到的字節流時,碰見此字符或者字符串則認爲收到一個完整的消息 
2.爲每一個消息定義一個長度,應用層收到指定長度的字節流則認爲收到了一個完整的消息
消息分隔標識(separator)、消息頭(header)、消息體(body)
 len | message_id | data 

 |separator |     header   | body |
 | len       | message_id | data

8. 粘包:
TCP粘包是指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。 
    1.發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一包數據。若連續發送幾回的數據都不多,一般TCP會根據優化算法把這些數據合成一包後一次發送出去,這樣接收方就收到了粘包數據。
    2.接收方引發的粘包是因爲接收方用戶進程不及時接收數據,從而致使粘包現象。這是由於接收方先把收到的數據放在系統接收緩衝區,用戶進程從該緩衝區取數據,若下一包數據到達時前一包數據還沒有被用戶進程取走,則下一包數據放到系統接收緩衝區時就接到前一包數據以後,而用戶進程根據預先設定的緩衝區大小從系統接收緩衝區取數據,這樣就一次取到了多包數據

解決措施:
    1.對於發送方引發的粘包現象,用戶可經過編程設置來避免,TCP提供了強制數據當即傳送的操做指令push,TCP軟件接收到該操做指令後,就當即將本段數據發送出去,而沒必要等待發送緩衝區滿;
TCP-NO-DELAY-關閉了優化算法,不推薦
    2.對於接收方引發的粘包,則可經過優化程序設計、精簡接收進程工做量、提升接收進程優先級等措施,使其及時接收數據,從而儘可能避免出現粘包現象-當發送頻率高時依然可能出現粘包
    3.接收方控制,將一包數據按結構字段,人爲控制分屢次接收,而後合併,經過這種手段來避免粘包。-效率低
    4.接收方建立一預處理線程,對接收到的數據包進行預處理,將粘連的包分開

分包算法思路:
基本思路是首先將待處理的接收數據(長度設爲m)強行轉換成預約的結構數據形式,並從中取出數據結構長度字段,即n,然後根據n計算獲得第一包數據長度
1) 若n<m,則代表數據流包含多包數據,從其頭部截取n個字節存入臨時緩衝區,剩餘部分數據一次繼續循環處理,直至結束。
2) 若n=m,則代表數據流內容剛好是一完整結構數據,直接將其存入臨時緩衝區便可。
3) 若n>m,則代表數據流內容尚不夠構成一個完整結構數據,需留待與下一包數據合併後再行處理。

五.正文之場景管理、ai、腳本

 AOI: (Area Of Interest),廣義上,AOI系統支持任何遊戲世界中的物體個體對必定半徑範圍內發生的事件進行處理;但MMOPRG上絕大多數需求只是對半徑範圍內發生的物體離開/進入事件進行處理。當你進入一個遊戲場景時,若是你能看到其餘玩家,那背後AOI系統就正在運做.

    1. 很容易想象,AOI的需求最簡單的作法是全世界玩家信息所有同步給客戶端。這個方案是O(n^2)的複雜度,對服務器來講是不能承受之重。但若是是超小地圖十人如下的特殊需求倒多是個簡潔的方案。
    2. 比較流行的方案是網格法,簡單,高效:將地圖按設定的格子大小劃分爲網格,設玩家移動到某座標,咱們很容易地將玩家納入該座標所屬的網格G的玩家鏈中,而這個玩家的可見集能夠簡單地將以網格G爲中心的九宮格中的玩家鏈聚合而獲得。而要得到兩次移動間的可見集差別,也非難事.

轉自雲風Blog:
所謂 AOI ( Area Of Interest ) ,大體有兩個用途。
    一則是解決 NPC 的 AI 事件觸發問題。遊戲場景中有衆多的 NPC ,比 PC 大體要多一個數量級。NPC 的 AI 觸發條件每每是和其它 NPC 或 PC 距離接近。若是沒有 AOI 模塊,每一個 NPC 都須要遍歷場景中其它對象,判斷與之距離。這個檢索量是很是巨大的(複雜度 O(N*N) )。通常咱們會設計一個 AOI 模塊,統一處理,並優化比較次數,當兩個對象距離接近時,以消息的形式通知它們。
    二則用於減小向 PC 發送的同步消息數量。把離 PC 較遠的物體狀態變化的消息過濾掉。PC 身上能夠帶一個附近對象列表,由 AOI 消息來增減這個列表的內容。
在服務器上,咱們通常推薦把 AOI 模塊作成一個獨立服務 。場景模塊通知它改變對象的位置信息。AOI 服務則發送 AOI 消息給場景
AOI 的傳統實現方法大體有三種:

第一,也是最苯的方案。直接按期比較全部對象間的關係,發現可以觸發 AOI 事件就發送消息。這種方案實現起來至關簡潔,幾乎不可能有 bug ,能夠用來驗證服務協議的正確性。在場景中對象不對的狀況下其實也是不錯的一個方案。若是咱們獨立出來的話,利用一個單獨的核,其實能夠按期處理至關大的對象數量。

第二,空間切割監視的方法。把場景劃分爲等大的格子,在每一個格子裏樹立燈塔。在對象進入或退出格子時,維護每一個燈塔上的對象列表。對於每一個燈塔仍是 O(N * N) 的複雜度,但因爲把對象數據量大量降了下來,因此性能要好的多,實現也很容易。缺點是,存儲空間不只和對象數量有關,還和場景大小有關。更浪費內存。且當場景規模大過對象數量規模時,性能還會降低。由於要遍歷整個場景。對大地圖不太合適。這裏還有一些優化技巧,好比能夠把格子劃分爲六邊形 的。

第三,使用十字鏈表 (3d 空間則再增長一個鏈表維度) 保存一系列線段,當線段移動時觸發 AOI 事件。算法不展開解釋,這個用的不少應該搜的到。優勢是能夠混用於不一樣半徑的 AOI 區域。

2.AI
    1.怪物AI
    2.NPC AI
    3.世界環境AI
實現方法:狀態機
 其餘:
 尋路:A*
 神經網絡
 遺傳算法

3.腳本語言的選擇:
Lua/Python/Erlang
Groovy/JRuby/Scala/Fantom/JPython-五大基於JVM的語言
  做用:可應用於部分應用層邏輯常常發生變化的系統。如任務系統。以在不須要從新編譯整個工程的狀況下調整、 測試和修改遊戲運行的機制和特性
六.正文之開源網絡遊戲服務器

魔獸世界模擬器
mangosTrinity  TrinityCore2

天堂2模擬器
L2J

永恆之塔模擬器

Arianne

七.正文之參考書籍,博客
1.雲風Blog  ttp://codingnow.com/ 2.書籍<大型多人在線遊戲開發><網絡遊戲服務器編程><UNIX網絡編程> 注:有部份內容來自網絡,謝謝大家!
相關文章
相關標籤/搜索