MMORPG服務器架構

一.摘要

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

二.關鍵詞

網絡協議 網絡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網絡編程>

注:有部分內容來自網絡,謝謝你們!

 

posted @ 2019-06-27 12:23 開發者里程碑 閱讀(...) 評論(...) 編輯 收藏
内部期期公开一波中特