棋牌游戲服務器架構: 總體設計


      首先要說明的是, 這個棋牌游戲的服務器架構參考了網狐棋牌的架構。網狐棋牌最令人印象深刻的是其穩定性和高網絡負載。它的一份壓力測試報告上指出:一台雙核r的INTEL Xeon 2.8CPU加上2G內存和使用共享100M光纖的機子能夠支持5000人同時在線游戲。

      在研究其服務器框架后發現,它的網絡部分確實是比較優化的。它主要采用了Windows提供的IO完成端口來實現其網絡組件。本服務器雖然參考了其設計,但是還是有很大的不同,因為這個服務器框架主要是用在linux系統之上,而網狐棋牌是基於Windows平台的,嚴重依賴於windows sdk。這個架構延續了網狐棋牌在網絡組件所作的努力,這個棋牌的服務器也使用異步IO作為網絡的工作方式,更為徹底的是其數據庫也是采用異步架構。boost::asio提供了一個異步框架,所以它的幾個核心組件: TCPServerService, TimerService, DatabaseService, AsyncService中都可以看到boost::asio的影子。

,  圖1是總體架構圖。從圖上我們看到服務器的整體架構分為三層:Libraries, Core和Applications。Core層基於Libraries實現,而Applications使用Core層提供的服務,並且要監聽Core層的異步事件(Socket、Database等)。

   圖1  棋牌游戲服務器端總架構

Libraries 主要由4個庫組成,其中boost::thread是一個跨平台的線程庫,boost::asio是跨平台的異步IO庫,protobuf則是用來序列化服務器和客戶端協議的, libpq是開源數據庫postgresql提供的客戶端的官方接口,支持異步數據庫操作。

Core 主要由4個Service組成,它們建立在Libraries的基礎之上。給應用層提供了網絡,數據庫和定時器功能。AsyncService主要是Core內部自己使用。TimerService提供定時器功能,TCPServerServic管理着客戶端來的連接。而DatabaseService提供基本的數據庫訪問功能。

Applications是基於Core實現的4種服務器,它們管理着游戲信息,提供登錄以及處理游戲邏輯的功能。下面是用戶與這些服務器交互的一個經典流程:

      1) 客戶端將用戶名和密碼發送給LogonServer登錄,在登錄驗證成功以后,將游戲列表返回給客戶端。

      2) 玩家選擇具體游戲進入房間時,客戶端發送請求給RoomServer,RoomServer將房間的信息返回給客戶端顯示

      3) 玩家選擇桌子坐下,游戲開始。客戶端將游戲動作發送給相應的RoomServer, RoomServer將操作解析后轉發給游戲邏輯模塊進行處理,並將處理結果返回給客戶端。

這幾個服務器這間的關系是:

      1) CenterServer維護游戲列表信息和房間信息;

      2) LogonServer定時從CenterServer取回游戲列表信息和房間信息;

      3) RoomServer在啟動時向CenterServer注冊,在關閉時從CenterServer注銷, 以玩家進入房間時通知CenterServer更新在線人數。同時像LogonServer一樣定時連接CenterServer更新游戲列表和房間信息。

1 Libraries層

      boost::asio是一個異步IO庫,提供了一個通用的異步框架,並提供了基本的socket的異步接口,它的主要功能是響應程序的異步IO請求,在操作完成以后,將其加入到一個完成隊列之中, 在這個完成隊列上有一些工作線程在等着,這些工作線程從完成隊列上取出已經完成的操作,調用上層應用提供的一個完成函數--completaion handler。asio庫是通過學實現Proactor模式來完成這些工作的,在Windows是直接基於I/O completion port,而在類Unix系統中,是基於epool等函數使用Reactor模式來模擬的。 

      libpq是開源數據庫postgresql提供的客戶端接口庫。這里選用postgresql是因為postgresql的跨平台性以及其穩定性和高性能,另一方面是由於我對這個數據庫比較地熟悉。Libpq也對數據庫的連接、查詢、更新等提供了異步實現。可以和boost::asio結合在一起提供統一地異步操作接口。

      boost::thread庫是用C++實現的一個跨平台的線程庫, 在C++11中,它已經被納入到了標准庫中。這個庫在這里主要用來實現一個線程池,作為boost::asio的工作線程。主要是由Core層的AsyncService來維護。代碼的其他地方不直接啟動線程。但是在異步操作的完成函數中,對那些共享數據需要加鎖保護。

      protobuf庫是Google發布的一個開源的用來序列化對象的高性能的庫,它支持多種語言,比如C++,Java,flash 等等。同時還將字節序等瑣碎的東西封裝起來了,方便上層應用。

2 Core層

      核心層由4個Service: AsyncService、TCPServerService、TimerService、DatabaseService組成。下面是關於它們的基本描述. 

      AttemptService是Core內部使用的,它封裝了boost::asio和ThreadPool的功能,提供給其他幾個Service使用。從名字上可以看出,他的主要功能是給其他幾個Service提供異步調度,這是通過boost::asio提供的功能來實現的,而ThreadPool是提供給boost::asio作為工作線程的。

      TCPServerService有一個連接池,管理着客戶端來的連接。內部通過AsyncService將socket讀寫完成消息,通過應用層注冊進來的TCPServiceObserver通知到調到應用層去。它和Applications的交互包括:

      1)  Applications 調用 SetObserver注冊用來接收網絡讀寫完成消息;

      2)  Applications 調用 SendData 發送數據;

      3)  Core在accept, recv完成后調用 Applications注冊的Observer。

      TimerService提供了定時器的功能,Applications層可以直接使用它來創建定時器,取消定時器。設定時間到來時,TimerService會調用創建定時器時指定的一個回調函數。

      DatabaseService封裝了libpq,提供數據庫的基本操作。主要管理數據庫連接,執行查詢操作,執行存儲過程等。它的實現中有一個連接池。和socket操作一樣,它提供的數據庫操作都是異步執行的,所以Applications層需要實現DBServiceObserver來監聽操作結果。

3 Applications

      前面的無論是libraries還是core,都是死的,只有applications加入了邏輯,它們是棋牌服務器的主休。下面是關於它們的比較詳細的信息

3.1 CenterServer

            圖2  CenterServer與外界的交互圖

      CenterServer不直接與玩家進行交互,它主要的功能是管理游戲列表和房間信息,包括:

      1. 游戲類型信息: 棋牌游戲、休閑游戲、視頻游戲等。

      2. 游戲種類: 比如在棋牌游戲這個大類之下有:德州撲克、斗地主、升級等。

      3. 站點信息: 因為這個服務器架構完全支持分布式,所以還保存有站點的信息

      4. 房間信息: 維護當前有哪些房間以及房間當前的在線人數。

      CenterServer中有關游戲列表的信息是它在啟動的時候從ServerInfoDB這個數據庫加載的, 而它的房間信息來自RoomServer,RoomServer在啟動時將自己注冊進來,在關閉的時候從CenterServer中注銷自己。同時在玩家進入房間的時候,還會要求CenterServer更新在線人數。

  CenterServer還應該響應LogonServer和RoomServer的請求,將游戲列表和房間信息返回給它們。

3.2 LogonServer 

              圖3 LogonServer與外界交互圖

      LogonServer提供注冊新的游戲玩家服務並且處理游戲玩家的登錄請求。

      LogonServer需要和UserInfoDB交互,這些交互包括:

      1. 在注冊的時候寫入注冊玩家的信息。

      2.在玩家登錄的時候與數據庫玩家信息進行核對。

      LogonServer會定時地向CenterServer發送更新游戲列表和房間信息的請求,因為這些信息在不斷地變化,而LogonServer需要在玩家登錄時將這些信息返回給他們。

3.3 LogServer

 

  圖4  LogServer與外界的交互圖

      有時候,玩家可能會對游戲的過程產生懷疑,或者想回顧整個游戲的過程。這就需要服務器將游戲的過程以Log的形式存儲起來,供玩家檢查用。LogServer的就是用來響應玩家的核查的請求,然后從GameLogDB中將整個游戲過程返回給客戶端,客戶端以視頻地方式顯示給玩家。 

      玩家在請求檢查的時候,客戶端會將這局游戲的以及玩家的信息id發送到LogServer, LogServer根據游戲id的信息從GameLogDB取出日志信息返回給玩家。游戲的過程可以用結構化語言描述出來,本來postgresql直接支持Json,也就是說Log可以以JSON的形式存在數據庫之中,但是由於可能會有字節序的問題,所以Log的信息也要用protobuf序列化了再存入數據庫。LogServer在從數據庫中讀出日志后不用反序列化直接返回給客戶端反序列化。

3.4 RoomServer

      RoomServer可能是最重要的一類Server了,一個RoomServer會和一個游戲模塊結合在一起。它管理着游戲的一個房間,處理玩家進入房間,找桌子座下的請求,並將游戲相關的消息轉發給游戲模塊進行處理。不僅不同的游戲會有不同的RoomServer,即便是同一游戲,也可能有多個RoomServer, 比如對於德州撲克來說,就可能有vip房間,普通房間等等,同一類型的房間也可能有Room1,Room2,這個可以根據玩家量按需架設。圖5給出了RoomServer與外界交互的圖。

圖5 RoomServer與外界的交互圖 

      RoomServer啟動的時候,先要發送請求給CenterServer進行注冊,在關閉時要從CenterServer中注銷。同時還會定時通知CenterServer更新在線人數, 定時從CenterServer上取回最新的游戲列表和房間信息。

      RoomServer需要和玩家進行交互。玩家進入房間,找桌子座下等的請求都由RoomServer來處理,而游戲操作。比如說加注、發牌等 RoomServer會直接轉發給游戲模塊進行處理。

      RoomServer管理着一個在線用戶列表,在玩家進入房間,離開房間時這個列表隨之更新。這個列表中有關玩家的詳細信息是從數據庫UserInfoDB中加載到的。 玩家在進行游戲時,由於輸贏的關系,他的積分或者游戲幣會隨着變化,為了記錄這些變化, 需要與GameDB進行交互。

      管理員可以通過RoomServer來發布消息、踢出玩家、警告玩家、設置玩家權限、設置房間屬性等活動。

      玩家也可以通過RoomServer參與聊天(包括大廳公聊和私聊)。

      

4 交互協議

      客戶端和服務器進行交互時,傳遞的包需要使用protobuf來序列化。一個請求由一個container組成,container中可以包含一個或者多個請求包/應答包。每一個請求包和應答包都有如下基本結構:

 

圖6 服務器和客戶端通信的Package結構

nMainCmd 指示請求的類別,比如說游戲請求,房間管理請求等

nSubCmd  指請求的具體是什么,比如加注、踢出玩家等

nDataSize  指示pData字段的長度

pData     可以是任何消息,如果是一個結構,需要用protobuf序列化

5 數據庫

Database主要有3個: ServerInfoDB、UserInfoDB, GameDB。

ServerInfoDB: 主要存儲的是游戲列表的信息。這些信息包括—游戲種類列表、游戲類型列表和站點信息。

UserInfoDB: 主要存儲玩家相關的全局信息,包括玩家的 ID 號碼,帳戶名字,密碼,二級密碼,頭像,經驗數值,登陸次數,注冊地址,最后登陸地址等玩家屬性信息。

GameDB:  主要存儲的是玩家的游戲相關信息,例如游戲積分,勝局,和局,逃局,登陸時間等信息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM