激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

云服務器|WEB服務器|FTP服務器|郵件服務器|虛擬主機|服務器安全|DNS服務器|服務器知識|Nginx|IIS|Tomcat|

服務器之家 - 服務器技術 - 服務器知識 - 教你從頭寫游戲服務器框架

教你從頭寫游戲服務器框架

2019-09-16 21:01今日頭條Java的小本家 服務器知識

由于“越通用的代碼,就是越沒用的代碼”,所以在設計之初,我就認為應該使用分層的模式來構建整個系統。

教你從頭寫游戲服務器框架

由于“越通用的代碼,就是越沒用的代碼”,所以在設計之初,我就認為應該使用分層的模式來構建整個系統。按照游戲服務器的一般需求劃分,最基本的可以分為兩層:

底層基礎功能:包括通信、持久化等非常通用的部分,關注的是性能、易用性、擴展性等指標。

高層邏輯功能:包括具體的游戲邏輯,針對不同的游戲會有不同的設計。

我希望能有一個基本完整的“底層基礎功能”的框架,可以被復用于多個不同的游戲。由于目標是開發一個 適合獨立游戲開發 的游戲服務器框架。所以最基本的需求分析為:

功能性需求

并發:所有的服務器程序,都會碰到這個基本的問題:如何處理并發處理。一般來說,會有多線程、異步兩種技術。多線程編程在編碼上比較符合人類的思維習慣,但帶來了“鎖”這個問題。而異步非阻塞的模型,其程序執行的情況是比較簡單的,而且也能比較充分的利用硬件性能,但是問題是很多代碼需要以“回調”的形式編寫,對于復雜的業務邏輯來說,顯得非常繁瑣,可讀性非常差。雖然這兩種方案各有利弊,也有人結合這兩種技術希望能各取所長,但是我更傾向于基礎是使用異步、單線程、非阻塞的調度方式,因為這個方案是最清晰簡單的。為了解決“回調”的問題,我們可以在其上再添加其他的抽象層,比如協程或者添加線程池之類的技術予以改善。

通信:支持 請求響應 模式以及 通知 模式的通信(廣播視為一種多目標的通知)。游戲有很多登錄、買賣、打開背包之類的功能,都是明確的有請求和響應的。而大量的聯機游戲中,多個客戶端的位置、HP 等東西都需要經過網絡同步,其實就是一種“主動通知”的通信方式。

持久化:可以存取 對象 。游戲存檔的格式非常復雜,但其索引的需求往往都是根據玩家 ID 來讀寫就可以。在很多游戲主機如 PlayStation 上,以前的存檔都是可以以類似“文件”的方式存放在記憶卡里的。所以游戲持久化最基本的需求,就是一個 key-value 存取模型。當然,游戲中還會有更復雜的持久化需求,比如排行榜、拍賣行等,這些需求應該額外對待,不適合包含在一個最基本的通用底層中。

緩存:支持遠程、分布式的對象緩存。游戲服務基本上都是“帶狀態”的服務,因為游戲要求響應延遲非常苛刻,基本上都需要利用服務器進程的內存來存放過程數據。但是游戲的數據,往往是變化越快的,價值越低,比如經驗值、金幣、HP,而等級、裝備等變化比較慢的,價值則越高,這種特征,非常適合用一個緩存模型來處理。

協程:可以用 C++ 來編寫協程代碼,避免大量回調函數分割代碼。這個是對于異步代碼非常有用的特性,能大大提高代碼的可讀性和開發效率。特別是把很多底層涉及IO的功能,都提供了協程化 API,使用起來就會像同步的 API 一樣輕松愜意。

腳本:初步設想是支持可以用 Lua 來編寫業務邏輯。游戲需求變化是出了名快的,用腳本語言編寫業務邏輯正好能提供這方面的支持。實際上腳本在游戲行業里的使用非常廣泛。所以支持腳本,也是一個游戲服務器框架很重要的能力。

其他功能:包括定時器、服務器端的對象管理等等。這些功能很常用,所以也需要包含在框架中,但已經有很多成熟方案,所以只要選取常見易懂的模型即可。比如對象管理,我會采用類似 Unity 的組件模型來實現。

非功能性需求

靈活性:支持可替換的通信協議;可替換的持久化設備(如數據庫);可替換的緩存設備(如 memcached/redis);以靜態庫和頭文件的方式發布,不對使用者代碼做過多的要求。游戲的運營環境比較復雜,特別是在不同的項目之間,可能會使用不同的數據庫、不同的通信協議。但是游戲本身業務邏輯很多都是基于對象模型去設計的,所以應該有一層能夠基于“對象”來抽象所有這些底層功能的模型。這樣才能讓多個不同的游戲,都基于一套底層進行開發。

部署便利性:支持靈活的配置文件、命令行參數、環境變量的引用;支持單獨進程啟動,而無須依賴數據庫、消息隊列中間件等設施。一般游戲都會有至少三套運行環境,包括一個開發環境、一個內測環境、一個外測或運營環境。一個游戲的版本更新,往往需要更新多個環境。所以如何能盡量簡化部署就成為一個很重要的問題。我認為一個好的服務器端框架,應該能讓這個服務器端程序,在無配置、無依賴的情況下獨立啟動,以符合在開發、測試、演示環境下快速部署。并且能很簡單的通過配置文件、或者命令行參數的不同,在集群化下的外部測試或者運營環境下啟動。

性能:很多游戲服務器,都會使用異步非阻塞的方式來編程。因為異步非阻塞可以很好的提高服務器的吞吐量,而且可以很明確的控制多個用戶任務并發下的代碼執行順序,從而避免多線程鎖之類的復雜問題。所以這個框架我也希望是以異步非阻塞作為基本的并發模型。這樣做還有另外一個好處,就是可以手工的控制具體的進程,充分利用多核 CPU 服務器的性能。當然異步代碼可讀性因為大量的回調函數,會變得很難閱讀,幸好我們還可以用“協程”來改善這個問題。

擴展性:支持服務器之間的通信,進程狀態管理,類似 SOA 的集群管理。自動容災和自動擴容,其實關鍵點是服務進程的狀態同步和管理。我希望一個通用的底層,可以把所有的服務器間調用,都通過一個統一的集權管理模型管理起來,這樣就可以不再每個項目去關心集群間通信、尋址等問題。

一旦需求明確下來,基本的層級結構也可以設計了:

教你從頭寫游戲服務器框架

最后,整體的架構模塊類似:

教你從頭寫游戲服務器框架

通信模塊

對于通信模塊來說,需要有靈活的可替換協議的能力,就必須按一定的層次進行進一步的劃分。對于游戲來說,最底層的通信協議,一般會使用 TCP 和 UDP 這兩種,在服務器之間,也會使用消息隊列中間件一類通信軟件。框架必須要有能同事支持這幾通信協議的能力。故此設計了一個層次為: Transport

在協議層面,最基本的需求有“分包”“分發”“對象序列化”等幾種需求。如果要支持“請求-響應”模式,還需要在協議中帶上“序列號”的數據,以便對應“請求”和“響應”。另外,游戲通常都是一種“會話”式的應用,也就是一系列的請求,會被視為一次“會話”,這就需要協眾需要有類似 Session ID 這種數據。為了滿足這些需求,設計一個層次為: Protocol

擁有了以上兩個層次,是可以完成最基本的協議層能力了。但是,我們往往希望業務數據的協議包,能自動化的成為編程中的 對象,所以在處理消息體這里,需要一個可選的額外層次,用來把字節數組,轉換成對象。所以我設計了一個特別的處理器:ObjectProcessor ,去規范通信模塊中對象序列化、反序列化的接口。

教你從頭寫游戲服務器框架

Transport

此層次是為了統一各種不同的底層傳輸協議而設置的,最基本應該支持 TCP 和 UDP 這兩種協議。對于通信協議的抽象,其實在很多底層庫也做的非常好了,比如 Linux 的 socket 庫,其讀寫 API 甚至可以和文件的讀寫通用。C# 的 Socket 庫在 TCP 和 UDP 之間,其 api 也幾乎是完全一樣的。但是由于作用游戲服務器,很多適合還會接入一些特別的“接入層”,比如一些代理服務器,或者一些消息中間件,這些 API 可是五花八門的。另外,在 html5 游戲(比如微信小游戲)和一些頁游領域,還有用 HTTP 服務器作為游戲服務器的傳統(如使用 WebSocket 協議),這樣就需要一個完全不同的傳輸層了。

服務器傳輸層在異步模型下的基本使用序列,就是:

在主循環中,不斷嘗試讀取有什么數據可讀

如果上一步返回有數據到達了,則讀取數據

讀取數據處理后,需要發送數據,則向網絡寫入數據

根據上面三個特點,可以歸納出一個基本的接口:

classTransport{

public:

/**

*初始化Transport對象,輸入Config對象配置最大連接數等參數,可以是一個新建的Config對象。

*/

virtualintInit(Config*config)=0;

/**

*檢查是否有數據可以讀取,返回可讀的事件數。后續代碼應該根據此返回值循環調用Read()提取數據。

*參數fds用于返回出現事件的所有fd列表,len表示這個列表的最大長度。如果可用事件大于這個數字,并不影響后續可以Read()的次數。

*fds的內容,如果出現負數,表示有一個新的終端等待接入。

*/

virtualintPeek(int*fds,intlen)=0;

/**

*讀取網絡管道中的數據。數據放在輸出參數peer的緩沖區中。

*@parampeer參數是產生事件的通信對端對象。

*@return返回值為可讀數據的長度,如果是0表示沒有數據可以讀,返回-1表示連接需要被關閉。

*/

virtualintRead(Peer*peer)=0;

/**

*寫入數據,output_buf,buf_len為想要寫入的數據緩沖區,output_peer為目標隊端,

*返回值表示成功寫入了的數據長度。-1表示寫入出錯。

*/

virtualintWrite(constchar*output_buf,intbuf_len,constPeer&output_peer)=0;

/**

*關閉一個對端的連接

*/

virtualvoidClosePeer(constPeer&peer)=0;

/**

*關閉Transport對象。

*/

virtualvoidClose()=0;

}

在上面的定義中,可以看到需要有一個 Peer 類型。這個類型是為了代表通信的客戶端(對端)對象。在一般的 Linux 系統中,一般我們用 fd (File Description)來代表。但是因為在框架中,我們還需要為每個客戶端建立接收數據的緩存區,以及記錄通信地址等功能,所以在 fd 的基礎上封裝了一個這樣的類型。這樣也有利于把 UDP 通信以不同客戶端的模型,進行封裝。

///@brief此類型負責存放連接過來的客戶端信息和數據緩沖區

classPeer{

public:

intbuf_size_;///<緩沖區長度

char*constbuffer_;///<緩沖區起始地址

intproduced_pos_;///<填入了數據的長度

intconsumed_pos_;///<消耗了數據的長度

intGetFd()const;

voidSetFd(intfd);///獲得本地地址

conststructsockaddr_in&GetLocalAddr()const;

voidSetLocalAddr(conststructsockaddr_in&localAddr);///獲得遠程地址

conststructsockaddr_in&GetRemoteAddr()const;

voidSetRemoteAddr(conststructsockaddr_in&remoteAddr);

private:

intfd_;///<收發數據用的fd

structsockaddr_inremote_addr_;///<對端地址

structsockaddr_inlocal_addr_;///<本端地址

};

游戲使用 UDP 協議的特點:一般來說 UDP 是無連接的,但是對于游戲來說,是肯定需要有明確的客戶端的,所以就不能簡單用一個 UDP socket 的fd 來代表客戶端,這就造成了上層的代碼無法簡單在 UDP 和 TCP 之間保持一致。因此這里使用 Peer 這個抽象層,正好可以接近這個問題。這也可以用于那些使用某種消息隊列中間件的情況,因為可能這些中間件,也是多路復用一個 fd 的,甚至可能就不是通過使用 fd 的 API 來開發的。

對于上面的 Transport 定義,對于 TCP 的實現者來說,是非常容易能完成的。但是對于 UDP 的實現者來說,則需要考慮如何寵妃利用 Peer ,特別是 Peer.fd_ 這個數據。我在實現的時候,使用了一套虛擬的 fd 機制,通過一個客戶端的 IPv4 地址到 int 的對應 Map ,來對上層提供區分客戶端的功能。在 Linux 上,這些 IO 都可以使用 epoll 庫來實現,在 Peek() 函數中讀取 IO 事件,在 Read()/Write() 填上 socket 的調用就可以了。

另外,為了實現服務器之間的通信,還需要設計和 Tansport 對應的一個類型:Connector 。這個抽象基類,用于以客戶端模型對服務器發起請求。其設計和 Transport 大同小異。除了 Linux 環境下的 Connecotr ,我還實現了在 C# 下的代碼,以便用 Unity 開發的客戶端可以方便的使用。由于 .NET 本身就支持異步模型,所以其實現也不費太多功夫。

/**

*@brief客戶端使用的連接器類,代表傳輸協議,如TCP或UDP

*/

classConnector{

public:virtual~Connector(){}

/**

*@brief初始化建立連接等

*@paramconfig需要的配置

*@return0為成功

*/

virtualintInit(Config*config)=0;

/**

*@brief關閉

*/

virtualvoidClose()=0;

/**

*@brief讀取是否有網絡數據到來

*讀取有無數據到來,返回值為可讀事件的數量,通常為1

*如果為0表示沒有數據可以讀取。

*如果返回-1表示出現網絡錯誤,需要關閉此連接。

*如果返回-2表示此連接成功連上對端。

*@return網絡數據的情況

*/

virtualintPeek()=0;

/**

*@brief讀取網絡數

*讀取連接里面的數據,返回讀取到的字節數,如果返回0表示沒有數據,

*如果buffer_length是0,也會返回0,

*@return返回-1表示連接需要關閉(各種出錯也返回0)

*/

virtualintRead(char*ouput_buffer,intbuffer_length)=0;

/**

*@brief把input_buffer里的數據寫入網絡連接,返回寫入的字節數。

*@return如果返回-1表示寫入出錯,需要關閉此連接。

*/

virtualintWrite(constchar*input_buffer,intbuffer_length)=0;

protected:

Connector(){}

};

Protocol

對于通信“協議”來說,其實包含了許許多多的含義。在眾多的需求中,我所定義的這個協議層,只希望完成四個最基本的能力:

分包:從流式傳輸層切分出一個個單獨的數據單元,或者把多個“碎片”數據拼合成一個完整的數據單元的能力。一般解決這個問題,需要在協議頭部添加一個“長度”字段。

請求響應對應:這對于異步非阻塞的通信模式下,是非常重要的功能。因為可能在一瞬間發出了很多個請求,而回應則會不分先后的到達。協議頭部如果有一個不重復的“序列號”字段,就可以對應起哪個回應是屬于哪個請求的。

會話保持:由于游戲的底層網絡,可能會使用 UDP 或者 HTTP 這種非長連接的傳輸方式,所以要在邏輯上保持一個會話,就不能單純的依靠傳輸層。加上我們都希望程序有抗網絡抖動、斷線重連的能力,所以保持會話成為一個常見的需求。我參考在 Web 服務領域的會話功能,設計了一個 Session 功能,在協議中加上 Session ID 這樣的數據,就能比較簡單的保持會話。

分發:游戲服務器必定會包含多個不同的業務邏輯,因此需要多種不同數據格式的協議包,為了把對應格式的數據轉發。

除了以上三個功能,實際上希望在協議層處理的能力,還有很多,最典型的就是對象序列化的功能,還有壓縮、加密功能等等。我之所以沒有把對象序列化的能力放在 Protocol 中,原因是對象序列化中的“對象”本身是一個業務邏輯關聯性非常強的概念。在 C++ 中,并沒有完整的“對象”模型,也缺乏原生的反射支持,所以無法很簡單的把代碼層次通過“對象”這個抽象概念劃分開來。但是我也設計了一個 ObjectProcessor ,把對象序列化的支持,以更上層的形式結合到框架中。這個 Processor 是可以自定義對象序列化的方法,這樣開發者就可以自己選擇任何“編碼、解碼”的能力,而不需要依靠底層的支持。

至于壓縮和加密這一類功能,確實是可以放在 Protocol 層中實現,甚至可以作為一個抽象層次加入 Protocol ,可能只有一個 Protocol 層不足以支持這么豐富的功能,需要好像 Apache Mina 這樣,設計一個“調用鏈”的模型。但是為了簡單起見,我覺得在具體需要用到的地方,再額外添加 Protocol 的實現類就好,比如添加一個“帶壓縮功能的 TLV Protocol 類型”之類的。

消息本身被抽象成一個叫 Message 的類型,它擁有“服務名字”“會話ID”兩個消息頭字段,用以完成“分發”和“會話保持”功能。而消息體則被放在一個字節數組中,并記錄下字節數組的長度。

enumMessageType{

TypeError,///<錯誤的協議

TypeRequest,///<請求類型,從客戶端發往服務器

TypeResponse,///<響應類型,服務器收到請求后返回

TypeNotice///<通知類型,服務器主動通知客戶端

};

///@brief通信消息體的基類

///基本上是一個char[]緩沖區

structMessage{

public:

staticintMAX_MAESSAGE_LENGTH;

staticintMAX_HEADER_LENGTH;

MessageTypetype;///<此消息體的類型(MessageType)信息

virtual~Message();virtualMessage&operator=(constMessage&right);

/**

*@brief把數據拷貝進此包體緩沖區

*/

voidSetData(constchar*input_ptr,intinput_length);

///@brief獲得數據指針

inlinechar*GetData()const{

returndata_;

}

///@brief獲得數據長度

inlineintGetDataLen()const{

returndata_len_;

}

char*GetHeader()const;

intGetHeaderLen()const;

protected:

Message();

Message(constMessage&message);

private:

char*data_;//包體內容緩沖區

intdata_len_;//包體長度

};

根據之前設計的“請求響應”和“通知”兩種通信模式,需要設計出三種消息類型繼承于 Message,他們是:

Request 請求包

Response 響應包

Notice 通知包

Request 和 Response 兩個類,都有記錄序列號的 seq_id 字段,但 Notice 沒有。Protocol 類就是負責把一段 buffer 字節數組,轉換成 Message 的子類對象。所以需要針對三種 Message 的子類型都實現對應的 Encode() / Decode() 方法。

classProtocol{

public:

virtual~Protocol(){

}

/**

*@brief把請求消息編碼成二進制數據

*編碼,把msg編碼到buf里面,返回寫入了多長的數據,如果超過了len,則返回-1表示錯誤。

*如果返回0,表示不需要編碼,框架會直接從msg的緩沖區讀取數據發送。

*@parambuf目標數據緩沖區

*@paramoffset目標偏移量

*@paramlen目標數據長度

*@parammsg輸入消息對象

*@return編碼完成所用的字節數,如果<0表示出錯

*/

virtualintEncode(char*buf,intoffset,intlen,constRequest&msg)=0;

/**

*編碼,把msg編碼到buf里面,返回寫入了多長的數據,如果超過了len,則返回-1表示錯誤。

*如果返回0,表示不需要編碼,框架會直接從msg的緩沖區讀取數據發送。

*@parambuf目標數據緩沖區

*@paramoffset目標偏移量

*@paramlen目標數據長度

*@parammsg輸入消息對象

*@return編碼完成所用的字節數,如果<0表示出錯

*/

virtualintEncode(char*buf,intoffset,intlen,constResponse&msg)=0;

/**

*編碼,把msg編碼到buf里面,返回寫入了多長的數據,如果超過了len,則返回-1表示錯誤。

*如果返回0,表示不需要編碼,框架會直接從msg的緩沖區讀取數據發送。

*@parambuf目標數據緩沖區

*@paramoffset目標偏移量

*@paramlen目標數據長度

*@parammsg輸入消息對象

*@return編碼完成所用的字節數,如果<0表示出錯

*/

virtualintEncode(char*buf,intoffset,intlen,constNotice&msg)=0;

/**

*開始編碼,會返回即將解碼出來的消息類型,以便使用者構造合適的對象。

*實際操作是在進行“分包”操作。

*@parambuf輸入緩沖區

*@paramoffset輸入偏移量

*@paramlen緩沖區長度

*@parammsg_type輸出參數,表示下一個消息的類型,只在返回值>0的情況下有效,否則都是TypeError

*@return如果返回0表示分包未完成,需要繼續分包。如果返回-1表示協議包頭解析出錯。其他返回值表示這個消息包占用的長度。

*/

virtualintDecodeBegin(constchar*buf,intoffset,intlen,

MessageType*msg_type)=0;

/**

*解碼,把之前DecodeBegin()的buf數據解碼成具體消息對象。

*@paramrequest輸出參數,解碼對象會寫入此指針

*@return返回0表示成功,-1表示失敗。

*/

virtualintDecode(Request*request)=0;

/**

*解碼,把之前DecodeBegin()的buf數據解碼成具體消息對象。

*@paramrequest輸出參數,解碼對象會寫入此指針

*@return返回0表示成功,-1表示失敗。

*/

virtualintDecode(Response*response)=0;

/**

*解碼,把之前DecodeBegin()的buf數據解碼成具體消息對象。

*@paramrequest輸出參數,解碼對象會寫入此指針

*@return返回0表示成功,-1表示失敗。

*/

virtualintDecode(Notice*notice)=0;protected:

Protocol(){

}

};

這里有一點需要注意,由于 C++ 沒有內存垃圾搜集和反射的能力,在解釋數據的時候,并不能一步就把一個 char[] 轉換成某個子類對象,而必須分成兩步處理。

先通過 DecodeBegin() 來返回,將要解碼的數據是屬于哪個子類型的。同時完成分包的工作,通過返回值來告知調用者,是否已經完整的收到一個包。

調用對應類型為參數的 Decode() 來具體把數據寫入對應的輸出變量。

對于 Protocol 的具體實現子類,我首先實現了一個 LineProtocol ,是一個非常不嚴謹的,基于文本ASCII編碼的,用空格分隔字段,用回車分包的協議。用來測試這個框架是否可行。因為這樣可以直接通過 telnet 工具,來測試協議的編解碼。然后我按照 TLV (Type Length Value)的方法設計了一個二進制的協議。大概的定義如下:

協議分包: [消息類型:int:2] [消息長度:int:4] [消息內容:bytes:消息長度]

消息類型取值:

0x00 Error

0x01 Request

0x02 Response

0x03 Notice

教你從頭寫游戲服務器框架

一個名為 TlvProtocol 的類型完成對這個協議的實現。

Processor

處理器層是我設計用來對接具體業務邏輯的抽象層,它主要通過輸入參數 Request 和 Peer 來獲得客戶端的輸入數據,然后通過 Server 類的 Reply()/Inform() 來返回 Response 和 Notice 消息。實際上 Transport 和 Protocol 的子類們,都屬于 net 模塊,而各種 Processor 和 Server/Client 這些功能類型,屬于另外一個 processor 模塊。這樣設計的原因,是希望所有 processor 模塊的代碼單向的依賴 net 模塊的代碼,但反過來不成立。

Processor 基類非常簡單,就是一個處理函數回調函數入口 Process():

///@brief處理器基類,提供業務邏輯回調接口

classProcessor{

public:

Processor();

virtual~Processor();

/**

*初始化一個處理器,參數server為業務邏輯提供了基本的能力接口。

*/

virtualintInit(Server*server,Config*config=NULL);

/**

*處理請求-響應類型包實現此方法,返回值是0表示成功,否則會被記錄在錯誤日志中。

*參數peer表示發來請求的對端情況。其中Server對象的指針,可以用來調用Reply(),

*Inform()等方法。如果是監聽多個服務器,server參數則會是不同的對象。

*/

virtualintProcess(constRequest&request,constPeer&peer,

Server*server);

/**

*關閉清理處理器所占用的資源

*/

virtualintClose();

};

設計完 Transport/Protocol/Processor 三個通信處理層次后,就需要一個組合這三個層次的代碼,那就是 Server 類。這個類在 Init() 的時候,需要上面三個類型的子類作為參數,以組合成不同功能的服務器,如:

TlvProtocoltlv_protocol;//TypeLengthValue格式分包協議,需要和客戶端一致

TcpTransporttcp_transport;//使用TCP的通信協議,默認監聽0.0.0.0:6666

EchoProcessorecho_processor;//業務邏輯處理器

Serverserver;//DenOS的網絡服務器主對象

server.Init(&tcp_transport,&tlv_protocol,&echo_processor);//組裝一個游戲服務器對象:TLV編碼、TCP通信和回音服務

Server 類型還需要一個 Update() 函數,讓用戶進程的“主循環”不停的調用,用來驅動整個程序的運行。這個 Update() 函數的內容非常明確:

檢查網絡是否有數據需要處理(通過 Transport 對象)

有數據的話就進行解碼處理(通過 Protocol 對象)

解碼成功后進行業務邏輯的分發調用(通過 Processor 對象)

另外,Server 還需要處理一些額外的功能,比如維護一個會話緩存池(Session),提供發送 Response 和 Notice 消息的接口。當這些工作都完成后,整套系統已經可以用來作為一個比較“通用”的網絡消息服務器框架存在了。剩下的就是添加各種 Transport/Protocol/Processor 子類的工作。

classServer{

public:

Server();

virtual~Server();

/**

*初始化服務器,需要選擇組裝你的通信協議鏈

*/

intInit(Transport*transport,Protocol*protocol,Processor*processor,Config*config=NULL);

/**

*阻塞方法,進入主循環。

*/

voidStart();

/**

*需要循環調用驅動的方法。如果返回值是0表示空閑。其他返回值表示處理過的任務數。

*/

virtualintUpdate();

voidClosePeer(Peer*peer,boolis_clear=false);//關閉當個連接,is_clear表示是否最終整體清理

/**

*關閉服務器

*/

voidClose();

/**

*對某個客戶端發送通知消息,

*參數peer代表要通知的對端。

*/

intInform(constNotice¬ice,constPeer&peer);

/**

*對某個SessionID對應的客戶端發送通知消息,返回0表示可以發送,其他值為發送失敗。

*此接口能支持斷線重連,只要客戶端已經成功連接,并使用舊的SessionID,同樣有效。

*/

intInform(constNotice¬ice,conststd::string&session_id);

/**

*對某個客戶端發來的Request發回回應消息。

*參數response的成員seqid必須正確填寫,才能正確回應。

*返回0成功,其它值(-1)表示失敗。

*/

intReply(Response*response,constPeer&peer);

/**

*對某個SessionID對應的客戶端發送回應消息。

*參數response的seqid成員系統會自動填寫會話中記錄的數值。

*此接口能支持斷線重連,只要客戶端已經成功連接,并使用舊的SessionID,同樣有效。

*返回0成功,其它值(-1)表示失敗。

*/

intReply(Response*response,conststd::string&session_id);

/**

*會話功能

*/

Session*GetSession(conststd::string&session_id="",booluse_this_id=false);

Session*GetSessionByNumId(intsession_id=0);

boolIsExist(conststd::string&session_id);

};

有了 Server 類型,肯定也需要有 Client 類型。而 Client 類型的設計和 Server 類似,但就不是使用 Transport 接口作為傳輸層,而是 Connector 接口。不過 Protocol 的抽象層是完全重用的。Client 并不需要 Processor 這種形式的回調,而是直接傳入接受數據消息就發起回調的接口對象 ClientCallback。

classClientCallback{

public:

ClientCallback(){

}

virtual~ClientCallback(){

//Donothing

}

/**

*當連接建立成功時回調此方法。

*@return返回-1表示不接受這個連接,需要關閉掉此連接。

*/

virtualintOnConnected(){

return0;

}

/**

*當網絡連接被關閉的時候,調用此方法

*/

virtualvoidOnDisconnected(){//Donothing

}

/**

*收到響應,或者請求超時,此方法會被調用。

*@paramresponse從服務器發來的回應

*@return如果返回非0值,服務器會打印一行錯誤日志。

*/

virtualintCallback(constResponse&response){

return0;

}

/**

*當請求發生錯誤,比如超時的時候,返回這個錯誤

*@paramerr_code錯誤碼

*/

virtualvoidOnError(interr_code){

WARN_LOG("Therequestistimeout,err_code:%d",err_code);

}

/**

*收到通知消息時,此方法會被調用

*/

virtualintCallback(constNotice¬ice){

return0;

}

/**

*返回此對象是否應該被刪除。此方法會被在Callback()調用前調用。

*@return如果返回true,則會調用delete此對象的指針。

*/

virtualboolShouldBeRemoved(){

returnfalse;

}

};

classClient:publicUpdateable{

public:

Client();virtual~Client();

/**

*連接服務器

*@paramconnector傳輸協議,如TCP,UDP...

*@paramprotocol分包協議,如TLV,Line,TDR...

*@paramnotice_callback收到通知后觸發的回調對象,如果傳輸協議有“連接概念”(如TCP/TCONND),建立、關閉連接時也會調用。

*@paramconfig配置文件對象,將讀取以下配置項目:MAX_TRANSACTIONS_OF_CLIENT客戶端最大并發連接數;BUFFER_LENGTH_OF_CLIENT客戶端收包緩存;CLIENT_RESPONSE_TIMEOUT客戶端響應等待超時時間。

*@return返回0表示成功,其他表示失敗

*/

intInit(Connector*connector,Protocol*protocol,

ClientCallback*notice_callback=NULL,Config*config=NULL);

/**

*callback參數可以為NULL,表示不需要回應,只是單純的發包即可。

*/

virtualintSendRequest(Request*request,ClientCallback*callback=NULL);

/**

*返回值表示有多少數據需要處理,返回-1為出錯,需要關閉連接。返回0表示沒有數據需要處理。

*/

virtualintUpdate();

virtualvoidOnExit();

voidClose();

Connector*connector();

ClientCallback*notice_callback();

Protocol*protocol();

};

至此,客戶端和服務器端基本設計完成,可以直接通過編寫測試代碼,來檢查是否運行正常。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 久久国产精品区 | 国产亚洲在线 | 在线2区| 牛牛视频在线 | 黄网站免费入口 | 国产一区毛片 | 国产精品久久久久久238 | 一本色道久久99精品综合蜜臀 | 色综合网在线观看 | 激情夜色 | 小视频成人 | 久久久久亚洲国产精品 | 欧美精品一区二区视频 | 91精品国产一区二区三区动漫 | 午夜视频成人 | 国产九色在线播放九色 | 免费视频www在线观看 | 久久成人国产精品入口 | 亚洲国产视频网 | 一级尻逼视频 | 国产一区亚洲 | 免费看一级视频 | 亚洲一区成人在线 | 毛片在线视频在线播放 | 免费国产人成网站 | 国产精品99久久久久久宅女 | 欧美一区永久视频免费观看 | 亚洲成人网一区 | 成人av一二三区 | 久久国产精 | 暖暖免费观看高清完整版电影 | 黄视频网址 | 亚洲成人第一区 | 午夜视频免费在线观看 | 日韩美女电影 | japanese hot milf free av | 91av资源在线 | 欧美www| 福利免费在线观看 | 夜间福利网站 | 天天看夜夜爽 |