首先要說明的是, 這個(gè)棋牌游戲的服務(wù)器架構(gòu)參考了網(wǎng)狐棋牌的架構(gòu)趴拧。網(wǎng)狐棋牌最令人印象深刻的是其穩(wěn)定性和高網(wǎng)絡(luò)負(fù)載裆馒。它的一份壓力測(cè)試報(bào)告上指出:一臺(tái)雙核r的INTEL Xeon 2.8CPU加上2G內(nèi)存和使用共享100M光纖的機(jī)子能夠支持5000人同時(shí)在線游戲。
? ? ? 在研究其服務(wù)器框架后發(fā)現(xiàn)侈贷,它的網(wǎng)絡(luò)部分確實(shí)是比較優(yōu)化的卒暂。它主要采用了Windows提供的IO完成端口來實(shí)現(xiàn)其網(wǎng)絡(luò)組件。本服務(wù)器雖然參考了其設(shè)計(jì)铁追,但是還是有很大的不同,因?yàn)檫@個(gè)服務(wù)器框架主要是用在linux系統(tǒng)之上茫船,而網(wǎng)狐棋牌是基于Windows平臺(tái)的琅束,嚴(yán)重依賴于windows sdk。這個(gè)架構(gòu)延續(xù)了網(wǎng)狐棋牌在網(wǎng)絡(luò)組件所作的努力算谈,這個(gè)棋牌的服務(wù)器也使用異步IO作為網(wǎng)絡(luò)的工作方式涩禀,更為徹底的是其數(shù)據(jù)庫(kù)也是采用異步架構(gòu)。boost::asio提供了一個(gè)異步框架然眼,所以它的幾個(gè)核心組件: TCPServerService, TimerService, DatabaseService, AsyncService中都可以看到boost::asio的影子艾船。
,? 圖1是總體架構(gòu)圖高每。從圖上我們看到服務(wù)器的整體架構(gòu)分為三層:Libraries, Core和Applications屿岂。Core層基于Libraries實(shí)現(xiàn),而Applications使用Core層提供的服務(wù)鲸匿,并且要監(jiān)聽Core層的異步事件(Socket雁社、Database等)。
圖1? 棋牌游戲服務(wù)器端總架構(gòu)
Libraries 主要由4個(gè)庫(kù)組成晒骇,其中boost::thread是一個(gè)跨平臺(tái)的線程庫(kù),boost::asio是跨平臺(tái)的異步IO庫(kù),protobuf則是用來序列化服務(wù)器和客戶端協(xié)議的, libpq是開源數(shù)據(jù)庫(kù)postgresql提供的客戶端的官方接口洪囤,支持異步數(shù)據(jù)庫(kù)操作徒坡。
Core 主要由4個(gè)Service組成,它們建立在Libraries的基礎(chǔ)之上瘤缩。給應(yīng)用層提供了網(wǎng)絡(luò)喇完,數(shù)據(jù)庫(kù)和定時(shí)器功能。AsyncService主要是Core內(nèi)部自己使用剥啤。TimerService提供定時(shí)器功能锦溪,TCPServerServic管理著客戶端來的連接。而DatabaseService提供基本的數(shù)據(jù)庫(kù)訪問功能府怯。
Applications是基于Core實(shí)現(xiàn)的4種服務(wù)器刻诊,它們管理著游戲信息,提供登錄以及處理游戲邏輯的功能牺丙。下面是用戶與這些服務(wù)器交互的一個(gè)經(jīng)典流程:
? ? ? 1) 客戶端將用戶名和密碼發(fā)送給LogonServer登錄则涯,在登錄驗(yàn)證成功以后,將游戲列表返回給客戶端冲簿。
? ? ? 2) 玩家選擇具體游戲進(jìn)入房間時(shí)粟判,客戶端發(fā)送請(qǐng)求給RoomServer,RoomServer將房間的信息返回給客戶端顯示
? ? ? 3) 玩家選擇桌子坐下峦剔,游戲開始档礁。客戶端將游戲動(dòng)作發(fā)送給相應(yīng)的RoomServer, RoomServer將操作解析后轉(zhuǎn)發(fā)給游戲邏輯模塊進(jìn)行處理吝沫,并將處理結(jié)果返回給客戶端呻澜。
這幾個(gè)服務(wù)器這間的關(guān)系是:
? ? ? 1) CenterServer維護(hù)游戲列表信息和房間信息;
? ? ? 2) LogonServer定時(shí)從CenterServer取回游戲列表信息和房間信息;
? ? ? 3) RoomServer在啟動(dòng)時(shí)向CenterServer注冊(cè),在關(guān)閉時(shí)從CenterServer注銷野舶, 以玩家進(jìn)入房間時(shí)通知CenterServer更新在線人數(shù)易迹。同時(shí)像LogonServer一樣定時(shí)連接CenterServer更新游戲列表和房間信息。
1 Libraries層
? ? ? boost::asio是一個(gè)異步IO庫(kù)平道,提供了一個(gè)通用的異步框架睹欲,并提供了基本的socket的異步接口,它的主要功能是響應(yīng)程序的異步IO請(qǐng)求一屋,在操作完成以后窘疮,將其加入到一個(gè)完成隊(duì)列之中, 在這個(gè)完成隊(duì)列上有一些工作線程在等著,這些工作線程從完成隊(duì)列上取出已經(jīng)完成的操作冀墨,調(diào)用上層應(yīng)用提供的一個(gè)完成函數(shù)--completaion handler闸衫。asio庫(kù)是通過學(xué)實(shí)現(xiàn)Proactor模式來完成這些工作的,在Windows是直接基于I/O completion port诽嘉,而在類Unix系統(tǒng)中蔚出,是基于epool等函數(shù)使用Reactor模式來模擬的弟翘。
? ? ? libpq是開源數(shù)據(jù)庫(kù)postgresql提供的客戶端接口庫(kù)。這里選用postgresql是因?yàn)閜ostgresql的跨平臺(tái)性以及其穩(wěn)定性和高性能骄酗,另一方面是由于我對(duì)這個(gè)數(shù)據(jù)庫(kù)比較地熟悉稀余。Libpq也對(duì)數(shù)據(jù)庫(kù)的連接、查詢趋翻、更新等提供了異步實(shí)現(xiàn)睛琳。可以和boost::asio結(jié)合在一起提供統(tǒng)一地異步操作接口踏烙。
? ? ? boost::thread庫(kù)是用C++實(shí)現(xiàn)的一個(gè)跨平臺(tái)的線程庫(kù), 在C++11中师骗,它已經(jīng)被納入到了標(biāo)準(zhǔn)庫(kù)中。這個(gè)庫(kù)在這里主要用來實(shí)現(xiàn)一個(gè)線程池讨惩,作為boost::asio的工作線程辟癌。主要是由Core層的AsyncService來維護(hù)。代碼的其他地方不直接啟動(dòng)線程步脓。但是在異步操作的完成函數(shù)中愿待,對(duì)那些共享數(shù)據(jù)需要加鎖保護(hù)。
? ? ? protobuf庫(kù)是Google發(fā)布的一個(gè)開源的用來序列化對(duì)象的高性能的庫(kù)靴患,它支持多種語(yǔ)言仍侥,比如C++,Java,flash 等等。同時(shí)還將字節(jié)序等瑣碎的東西封裝起來了鸳君,方便上層應(yīng)用农渊。
2 Core層
? ? ? 核心層由4個(gè)Service: AsyncService、TCPServerService或颊、TimerService砸紊、DatabaseService組成。下面是關(guān)于它們的基本描述.
? ? ? AttemptService是Core內(nèi)部使用的囱挑,它封裝了boost::asio和ThreadPool的功能醉顽,提供給其他幾個(gè)Service使用。從名字上可以看出平挑,他的主要功能是給其他幾個(gè)Service提供異步調(diào)度游添,這是通過boost::asio提供的功能來實(shí)現(xiàn)的,而ThreadPool是提供給boost::asio作為工作線程的通熄。
? ? ? TCPServerService有一個(gè)連接池,管理著客戶端來的連接唆涝。內(nèi)部通過AsyncService將socket讀寫完成消息,通過應(yīng)用層注冊(cè)進(jìn)來的TCPServiceObserver通知到調(diào)到應(yīng)用層去唇辨。它和Applications的交互包括:
? ? ? 1)? Applications 調(diào)用 SetObserver注冊(cè)用來接收網(wǎng)絡(luò)讀寫完成消息;
? ? ? 2)? Applications 調(diào)用 SendData 發(fā)送數(shù)據(jù);
? ? ? 3)? Core在accept, recv完成后調(diào)用 Applications注冊(cè)的Observer廊酣。
? ? ? TimerService提供了定時(shí)器的功能,Applications層可以直接使用它來創(chuàng)建定時(shí)器,取消定時(shí)器赏枚。設(shè)定時(shí)間到來時(shí)亡驰,TimerService會(huì)調(diào)用創(chuàng)建定時(shí)器時(shí)指定的一個(gè)回調(diào)函數(shù)晓猛。
? ? ? DatabaseService封裝了libpq,提供數(shù)據(jù)庫(kù)的基本操作。主要管理數(shù)據(jù)庫(kù)連接凡辱,執(zhí)行查詢操作鞍帝,執(zhí)行存儲(chǔ)過程等。它的實(shí)現(xiàn)中有一個(gè)連接池煞茫。和socket操作一樣,它提供的數(shù)據(jù)庫(kù)操作都是異步執(zhí)行的摄凡,所以Applications層需要實(shí)現(xiàn)DBServiceObserver來監(jiān)聽操作結(jié)果续徽。
3 Applications
? ? ? 前面的無論是libraries還是core,都是死的,只有applications加入了邏輯亲澡,它們是棋牌服務(wù)器的主休钦扭。下面是關(guān)于它們的比較詳細(xì)的信息
3.1 CenterServer
? ? ? ? ? ? 圖2? CenterServer與外界的交互圖
? ? ? CenterServer不直接與玩家進(jìn)行交互,它主要的功能是管理游戲列表和房間信息,包括:
? ? ? 1. 游戲類型信息: 棋牌游戲床绪、休閑游戲客情、視頻游戲等。
? ? ? 2. 游戲種類: 比如在棋牌游戲這個(gè)大類之下有:德州撲克癞己、斗地主膀斋、升級(jí)等。
? ? ? 3. 站點(diǎn)信息: 因?yàn)檫@個(gè)服務(wù)器架構(gòu)完全支持分布式痹雅,所以還保存有站點(diǎn)的信息
? ? ? 4. 房間信息: 維護(hù)當(dāng)前有哪些房間以及房間當(dāng)前的在線人數(shù)仰担。
? ? ? CenterServer中有關(guān)游戲列表的信息是它在啟動(dòng)的時(shí)候從ServerInfoDB這個(gè)數(shù)據(jù)庫(kù)加載的, 而它的房間信息來自RoomServer,RoomServer在啟動(dòng)時(shí)將自己注冊(cè)進(jìn)來绩社,在關(guān)閉的時(shí)候從CenterServer中注銷自己摔蓝。同時(shí)在玩家進(jìn)入房間的時(shí)候,還會(huì)要求CenterServer更新在線人數(shù)愉耙。
CenterServer還應(yīng)該響應(yīng)LogonServer和RoomServer的請(qǐng)求贮尉,將游戲列表和房間信息返回給它們。
3.2 LogonServer
? ? ? ? ? ? ? 圖3 LogonServer與外界交互圖
? ? ? LogonServer提供注冊(cè)新的游戲玩家服務(wù)并且處理游戲玩家的登錄請(qǐng)求朴沿。
? ? ? LogonServer需要和UserInfoDB交互猜谚,這些交互包括:
? ? ? 1. 在注冊(cè)的時(shí)候?qū)懭胱?cè)玩家的信息。
? ? ? 2.在玩家登錄的時(shí)候與數(shù)據(jù)庫(kù)玩家信息進(jìn)行核對(duì)悯仙。
? ? ? LogonServer會(huì)定時(shí)地向CenterServer發(fā)送更新游戲列表和房間信息的請(qǐng)求龄毡,因?yàn)檫@些信息在不斷地變化,而LogonServer需要在玩家登錄時(shí)將這些信息返回給他們锡垄。
3.3 LogServer
? 圖4? LogServer與外界的交互圖
? ? ? 有時(shí)候沦零,玩家可能會(huì)對(duì)游戲的過程產(chǎn)生懷疑,或者想回顧整個(gè)游戲的過程货岭。這就需要服務(wù)器將游戲的過程以Log的形式存儲(chǔ)起來路操,供玩家檢查用疾渴。LogServer的就是用來響應(yīng)玩家的核查的請(qǐng)求,然后從GameLogDB中將整個(gè)游戲過程返回給客戶端屯仗,客戶端以視頻地方式顯示給玩家搞坝。
? ? ? 玩家在請(qǐng)求檢查的時(shí)候,客戶端會(huì)將這局游戲的以及玩家的信息id發(fā)送到LogServer, LogServer根據(jù)游戲id的信息從GameLogDB取出日志信息返回給玩家魁袜。游戲的過程可以用結(jié)構(gòu)化語(yǔ)言描述出來桩撮,本來postgresql直接支持Json,也就是說Log可以以JSON的形式存在數(shù)據(jù)庫(kù)之中峰弹,但是由于可能會(huì)有字節(jié)序的問題店量,所以Log的信息也要用protobuf序列化了再存入數(shù)據(jù)庫(kù)。LogServer在從數(shù)據(jù)庫(kù)中讀出日志后不用反序列化直接返回給客戶端反序列化鞠呈。
3.4 RoomServer
? ? ? RoomServer可能是最重要的一類Server了融师,一個(gè)RoomServer會(huì)和一個(gè)游戲模塊結(jié)合在一起。它管理著游戲的一個(gè)房間蚁吝,處理玩家進(jìn)入房間旱爆,找桌子座下的請(qǐng)求,并將游戲相關(guān)的消息轉(zhuǎn)發(fā)給游戲模塊進(jìn)行處理窘茁。不僅不同的游戲會(huì)有不同的RoomServer怀伦,即便是同一游戲,也可能有多個(gè)RoomServer庙曙, 比如對(duì)于德州撲克來說空镜,就可能有vip房間,普通房間等等捌朴,同一類型的房間也可能有Room1,Room2,這個(gè)可以根據(jù)玩家量按需架設(shè)吴攒。圖5給出了RoomServer與外界交互的圖。
圖5 RoomServer與外界的交互圖
? ? ? RoomServer啟動(dòng)的時(shí)候砂蔽,先要發(fā)送請(qǐng)求給CenterServer進(jìn)行注冊(cè)洼怔,在關(guān)閉時(shí)要從CenterServer中注銷。同時(shí)還會(huì)定時(shí)通知CenterServer更新在線人數(shù), 定時(shí)從CenterServer上取回最新的游戲列表和房間信息左驾。
? ? ? RoomServer需要和玩家進(jìn)行交互镣隶。玩家進(jìn)入房間,找桌子座下等的請(qǐng)求都由RoomServer來處理诡右,而游戲操作安岂。比如說加注楔绞、發(fā)牌等 RoomServer會(huì)直接轉(zhuǎn)發(fā)給游戲模塊進(jìn)行處理谓罗。
? ? ? RoomServer管理著一個(gè)在線用戶列表,在玩家進(jìn)入房間审编,離開房間時(shí)這個(gè)列表隨之更新猜煮。這個(gè)列表中有關(guān)玩家的詳細(xì)信息是從數(shù)據(jù)庫(kù)UserInfoDB中加載到的次员。 玩家在進(jìn)行游戲時(shí)败许,由于輸贏的關(guān)系,他的積分或者游戲幣會(huì)隨著變化淑蔚,為了記錄這些變化, 需要與GameDB進(jìn)行交互市殷。
? ? ? 管理員可以通過RoomServer來發(fā)布消息、踢出玩家刹衫、警告玩家醋寝、設(shè)置玩家權(quán)限、設(shè)置房間屬性等活動(dòng)带迟。
? ? ? 玩家也可以通過RoomServer參與聊天(包括大廳公聊和私聊)甥桂。
4 交互協(xié)議
? ? ? 客戶端和服務(wù)器進(jìn)行交互時(shí),傳遞的包需要使用protobuf來序列化邮旷。一個(gè)請(qǐng)求由一個(gè)container組成,container中可以包含一個(gè)或者多個(gè)請(qǐng)求包/應(yīng)答包蝇摸。每一個(gè)請(qǐng)求包和應(yīng)答包都有如下基本結(jié)構(gòu):
圖6 服務(wù)器和客戶端通信的Package結(jié)構(gòu)
nMainCmd 指示請(qǐng)求的類別婶肩,比如說游戲請(qǐng)求,房間管理請(qǐng)求等
nSubCmd? 指請(qǐng)求的具體是什么貌夕,比如加注律歼、踢出玩家等
nDataSize? 指示pData字段的長(zhǎng)度
pData? ? 可以是任何消息,如果是一個(gè)結(jié)構(gòu)啡专,需要用protobuf序列化
5 數(shù)據(jù)庫(kù)
Database主要有3個(gè): ServerInfoDB险毁、UserInfoDB, GameDB。
ServerInfoDB: 主要存儲(chǔ)的是游戲列表的信息们童。這些信息包括—游戲種類列表畔况、游戲類型列表和站點(diǎn)信息。
UserInfoDB: 主要存儲(chǔ)玩家相關(guān)的全局信息慧库,包括玩家的 ID 號(hào)碼跷跪,帳戶名字,密碼齐板,二級(jí)密碼吵瞻,頭像,經(jīng)驗(yàn)數(shù)值甘磨,登陸次數(shù)橡羞,注冊(cè)地址,最后登陸地址等玩家屬性信息济舆。
GameDB:? 主要存儲(chǔ)的是玩家的游戲相關(guān)信息卿泽,例如游戲積分,勝局吗冤,和局又厉,逃局九府,登陸時(shí)間等信息