首先要說明的是, 這個棋牌游戲的服務(wù)器架構(gòu)參考了網(wǎng)狐棋牌的架構(gòu)。網(wǎng)狐棋牌最令人印象深刻的是其穩(wěn)定性和高網(wǎng)絡(luò)負載。它的一份壓力測試報告上指出:一臺雙核r的INTEL Xeon 2.8CPU加上2G內(nèi)存和使用共享100M光纖的機子能夠支持5000人同時在線游戲。
在研究其服務(wù)器框架后發(fā)現(xiàn),它的網(wǎng)絡(luò)部分確實是比較優(yōu)化的。它主要采用了Windows提供的IO完成端口來實現(xiàn)其網(wǎng)絡(luò)組件。本服務(wù)器雖然參考了其設(shè)計,但是還是有很大的不同,因為這個服務(wù)器框架主要是用在linux系統(tǒng)之上,而網(wǎng)狐棋牌是基于Windows平臺的,嚴重依賴于windows sdk。這個架構(gòu)延續(xù)了網(wǎng)狐棋牌在網(wǎng)絡(luò)組件所作的努力,這個棋牌的服務(wù)器也使用異步IO作為網(wǎng)絡(luò)的工作方式,更為徹底的是其數(shù)據(jù)庫也是采用異步架構(gòu)。boost::asio提供了一個異步框架,所以它的幾個核心組件: TCPServerService, TimerService, DatabaseService, AsyncService中都可以看到boost::asio的影子。
, 圖1是總體架構(gòu)圖。從圖上我們看到服務(wù)器的整體架構(gòu)分為三層:Libraries, Core和Applications。Core層基于Libraries實現(xiàn),而Applications使用Core層提供的服務(wù),并且要監(jiān)聽Core層的異步事件(Socket、Database等)。
Libraries 主要由4個庫組成,其中boost::thread是一個跨平臺的線程庫,boost::asio是跨平臺的異步IO庫,protobuf則是用來序列化服務(wù)器和客戶端協(xié)議的, libpq是開源數(shù)據(jù)庫postgresql提供的客戶端的官方接口,支持異步數(shù)據(jù)庫操作。
Core 主要由4個Service組成,它們建立在Libraries的基礎(chǔ)之上。給應(yīng)用層提供了網(wǎng)絡(luò),數(shù)據(jù)庫和定時器功能。AsyncService主要是Core內(nèi)部自己使用。TimerService提供定時器功能,TCPServerServic管理著客戶端來的連接。而DatabaseService提供基本的數(shù)據(jù)庫訪問功能。
Applications是基于Core實現(xiàn)的4種服務(wù)器,它們管理著游戲信息,提供登錄以及處理游戲邏輯的功能。下面是用戶與這些服務(wù)器交互的一個經(jīng)典流程:
1) 客戶端將用戶名和密碼發(fā)送給LogonServer登錄,在登錄驗證成功以后,將游戲列表返回給客戶端。
2) 玩家選擇具體游戲進入房間時,客戶端發(fā)送請求給RoomServer,RoomServer將房間的信息返回給客戶端顯示
3) 玩家選擇桌子坐下,游戲開始?蛻舳藢⒂螒騽幼靼l(fā)送給相應(yīng)的RoomServer, RoomServer將操作解析后轉(zhuǎn)發(fā)給游戲邏輯模塊進行處理,并將處理結(jié)果返回給客戶端。
這幾個服務(wù)器這間的關(guān)系是:
1) CenterServer維護游戲列表信息和房間信息;
2) LogonServer定時從CenterServer取回游戲列表信息和房間信息;
3) RoomServer在啟動時向CenterServer注冊,在關(guān)閉時從CenterServer注銷, 以玩家進入房間時通知CenterServer更新在線人數(shù)。同時像LogonServer一樣定時連接CenterServer更新游戲列表和房間信息。
1 Libraries層
boost::asio是一個異步IO庫,提供了一個通用的異步框架,并提供了基本的socket的異步接口,它的主要功能是響應(yīng)程序的異步IO請求,在操作完成以后,將其加入到一個完成隊列之中, 在這個完成隊列上有一些工作線程在等著,這些工作線程從完成隊列上取出已經(jīng)完成的操作,調(diào)用上層應(yīng)用提供的一個完成函數(shù)--completaion handler。asio庫是通過學實現(xiàn)Proactor模式來完成這些工作的,在Windows是直接基于I/O completion port,而在類Unix系統(tǒng)中,是基于epool等函數(shù)使用Reactor模式來模擬的。
libpq是開源數(shù)據(jù)庫postgresql提供的客戶端接口庫。這里選用postgresql是因為postgresql的跨平臺性以及其穩(wěn)定性和高性能,另一方面是由于我對這個數(shù)據(jù)庫比較地熟悉。Libpq也對數(shù)據(jù)庫的連接、查詢、更新等提供了異步實現(xiàn)?梢院蚥oost::asio結(jié)合在一起提供統(tǒng)一地異步操作接口。
boost::thread庫是用C++實現(xiàn)的一個跨平臺的線程庫, 在C++11中,它已經(jīng)被納入到了標準庫中。這個庫在這里主要用來實現(xiàn)一個線程池,作為boost::asio的工作線程。主要是由Core層的AsyncService來維護。代碼的其他地方不直接啟動線程。但是在異步操作的完成函數(shù)中,對那些共享數(shù)據(jù)需要加鎖保護。
protobuf庫是Google發(fā)布的一個開源的用來序列化對象的高性能的庫,它支持多種語言,比如C++,Java,flash 等等。同時還將字節(jié)序等瑣碎的東西封裝起來了,方便上層應(yīng)用。
2 Core層
核心層由4個Service: AsyncService、TCPServerService、TimerService、DatabaseService組成。下面是關(guān)于它們的基本描述.
AttemptService是Core內(nèi)部使用的,它封裝了boost::asio和ThreadPool的功能,提供給其他幾個Service使用。從名字上可以看出,他的主要功能是給其他幾個Service提供異步調(diào)度,這是通過boost::asio提供的功能來實現(xiàn)的,而ThreadPool是提供給boost::asio作為工作線程的。
TCPServerService有一個連接池,管理著客戶端來的連接。內(nèi)部通過AsyncService將socket讀寫完成消息,通過應(yīng)用層注冊進來的TCPServiceObserver通知到調(diào)到應(yīng)用層去。它和Applications的交互包括:
1) Applications 調(diào)用 SetObserver注冊用來接收網(wǎng)絡(luò)讀寫完成消息;
2) Applications 調(diào)用 SendData 發(fā)送數(shù)據(jù);
3) Core在accept, recv完成后調(diào)用 Applications注冊的Observer。
TimerService提供了定時器的功能,Applications層可以直接使用它來創(chuàng)建定時器,取消定時器。設(shè)定時間到來時,TimerService會調(diào)用創(chuàng)建定時器時指定的一個回調(diào)函數(shù)。
DatabaseService封裝了libpq,提供數(shù)據(jù)庫的基本操作。主要管理數(shù)據(jù)庫連接,執(zhí)行查詢操作,執(zhí)行存儲過程等。它的實現(xiàn)中有一個連接池。和socket操作一樣,它提供的數(shù)據(jù)庫操作都是異步執(zhí)行的,所以Applications層需要實現(xiàn)DBServiceObserver來監(jiān)聽操作結(jié)果。
3 Applications
前面的無論是libraries還是core,都是死的,只有applications加入了邏輯,它們是棋牌服務(wù)器的主休。下面是關(guān)于它們的比較詳細的信息
3.1 CenterServer
CenterServer不直接與玩家進行交互,它主要的功能是管理游戲列表和房間信息,包括:
1. 游戲類型信息: 棋牌游戲、休閑游戲、視頻游戲等。
2. 游戲種類: 比如在棋牌游戲這個大類之下有:德州撲克、斗地主、升級等。
3. 站點信息: 因為這個服務(wù)器架構(gòu)完全支持分布式,所以還保存有站點的信息
4. 房間信息: 維護當前有哪些房間以及房間當前的在線人數(shù)。
CenterServer中有關(guān)游戲列表的信息是它在啟動的時候從ServerInfoDB這個數(shù)據(jù)庫加載的, 而它的房間信息來自RoomServer,RoomServer在啟動時將自己注冊進來,在關(guān)閉的時候從CenterServer中注銷自己。同時在玩家進入房間的時候,還會要求CenterServer更新在線人數(shù)。
CenterServer還應(yīng)該響應(yīng)LogonServer和RoomServer的請求,將游戲列表和房間信息返回給它們。
3.2 LogonServer
LogonServer提供注冊新的游戲玩家服務(wù)并且處理游戲玩家的登錄請求。
LogonServer需要和UserInfoDB交互,這些交互包括:
1. 在注冊的時候?qū)懭胱酝婕业男畔ⅰ?/p>
2.在玩家登錄的時候與數(shù)據(jù)庫玩家信息進行核對。
LogonServer會定時地向CenterServer發(fā)送更新游戲列表和房間信息的請求,因為這些信息在不斷地變化,而LogonServer需要在玩家登錄時將這些信息返回給他們。
3.3 LogServer
有時候,玩家可能會對游戲的過程產(chǎn)生懷疑,或者想回顧整個游戲的過程。這就需要服務(wù)器將游戲的過程以Log的形式存儲起來,供玩家檢查用。LogServer的就是用來響應(yīng)玩家的核查的請求,然后從GameLogDB中將整個游戲過程返回給客戶端,客戶端以視頻地方式顯示給玩家。
玩家在請求檢查的時候,客戶端會將這局游戲的以及玩家的信息id發(fā)送到LogServer, LogServer根據(jù)游戲id的信息從GameLogDB取出日志信息返回給玩家。游戲的過程可以用結(jié)構(gòu)化語言描述出來,本來postgresql直接支持Json,也就是說Log可以以JSON的形式存在數(shù)據(jù)庫之中,但是由于可能會有字節(jié)序的問題,所以Log的信息也要用protobuf序列化了再存入數(shù)據(jù)庫。LogServer在從數(shù)據(jù)庫中讀出日志后不用反序列化直接返回給客戶端反序列化。
3.4 RoomServer
RoomServer可能是最重要的一類Server了,一個RoomServer會和一個游戲模塊結(jié)合在一起。它管理著游戲的一個房間,處理玩家進入房間,找桌子座下的請求,并將游戲相關(guān)的消息轉(zhuǎn)發(fā)給游戲模塊進行處理。不僅不同的游戲會有不同的RoomServer,即便是同一游戲,也可能有多個RoomServer, 比如對于德州撲克來說,就可能有vip房間,普通房間等等,同一類型的房間也可能有Room1,Room2,這個可以根據(jù)玩家量按需架設(shè)。圖5給出了RoomServer與外界交互的圖。
RoomServer啟動的時候,先要發(fā)送請求給CenterServer進行注冊,在關(guān)閉時要從CenterServer中注銷。同時還會定時通知CenterServer更新在線人數(shù), 定時從CenterServer上取回最新的游戲列表和房間信息。
RoomServer需要和玩家進行交互。玩家進入房間,找桌子座下等的請求都由RoomServer來處理,而游戲操作。比如說加注、發(fā)牌等 RoomServer會直接轉(zhuǎn)發(fā)給游戲模塊進行處理。
RoomServer管理著一個在線用戶列表,在玩家進入房間,離開房間時這個列表隨之更新。這個列表中有關(guān)玩家的詳細信息是從數(shù)據(jù)庫UserInfoDB中加載到的。 玩家在進行游戲時,由于輸贏的關(guān)系,他的積分或者游戲幣會隨著變化,為了記錄這些變化, 需要與GameDB進行交互。
管理員可以通過RoomServer來發(fā)布消息、踢出玩家、警告玩家、設(shè)置玩家權(quán)限、設(shè)置房間屬性等活動。
玩家也可以通過RoomServer參與聊天(包括大廳公聊和私聊)。
4 交互協(xié)議
客戶端和服務(wù)器進行交互時,傳遞的包需要使用protobuf來序列化。一個請求由一個container組成,container中可以包含一個或者多個請求包/應(yīng)答包。每一個請求包和應(yīng)答包都有如下基本結(jié)構(gòu):
nMainCmd 指示請求的類別,比如說游戲請求,房間管理請求等
nSubCmd 指請求的具體是什么,比如加注、踢出玩家等
nDataSize 指示pData字段的長度
pData 可以是任何消息,如果是一個結(jié)構(gòu),需要用protobuf序列化
5 數(shù)據(jù)庫
Database主要有3個: ServerInfoDB、UserInfoDB, GameDB。
ServerInfoDB: 主要存儲的是游戲列表的信息。這些信息包括—游戲種類列表、游戲類型列表和站點信息。
UserInfoDB: 主要存儲玩家相關(guān)的全局信息,包括玩家的 ID 號碼,帳戶名字,密碼,二級密碼,頭像,經(jīng)驗數(shù)值,登陸次數(shù),注冊地址,最后登陸地址等玩家屬性信息。
GameDB: 主要存儲的是玩家的游戲相關(guān)信息,例如游戲積分,勝局,和局,逃局,登陸時間等信息