陷陣之志浴井,有死無生 --趙信
如大家對網(wǎng)絡(luò)還不甚了解,請先參照本系列網(wǎng)絡(luò)介紹博文 游戲之網(wǎng)絡(luò)初篇
現(xiàn)在很少有游戲公司自己用原生Socket API手寫網(wǎng)絡(luò)框架了酪穿,一是增加工作量娱节,增加產(chǎn)品開發(fā)時長税产;二是如果綜合性能摊灭、效率咆贬、高吞吐量、高并發(fā)帚呼,及穩(wěn)健性來說掏缎,自己如果非大神,寫出來的很可能還沒別人已有的網(wǎng)絡(luò)框架好萝挤,自己還需經(jīng)大量測試驗證才能保證線上穩(wěn)定御毅,不容出錯根欧。所以怜珍,現(xiàn)在很多的Java游戲框架,基本上都是用Netty或Mina作為網(wǎng)絡(luò)通信框架的凤粗,Netty和Mina用起來有很多相似之處酥泛,知其一另一個上手就很容易今豆,但在很多人看來,Netty比Mina更好用(更適合游戲使用)柔袁,其一呆躲,Java游戲公司基本都是用Protobuf作為協(xié)議工具的,以應(yīng)對游戲各種功能玩法中前后端所需不同的數(shù)據(jù)字段讀取和傳輸要求捶索,及減少協(xié)議聯(lián)調(diào)出錯幾率插掂,Netty包中自帶對Protobuf的編解碼器,而Mina還需自己去實現(xiàn)Protobuf的編解碼器腥例;其二辅甥,Netty比Mina更好地支持多種協(xié)議,比如對Http的支持燎竖,Netty就比Mina好些璃弄,在《游戲之網(wǎng)絡(luò)初篇》中,游戲常用的協(xié)議Netty都支持构回,簡直為游戲量身打造夏块;其三,Netty比Mina更易使用纤掸,除了Netty自帶很多編解碼器外脐供,在對ByteBuf的操作比Mina的IoBuffer的操作更簡易,此外借跪,Netty支持ByteBuf零拷貝患民,一聽就是讓人省心的提高效率和節(jié)約內(nèi)存的高科技東東。
Netty 和 Mina都為韓國Trustin Lee開發(fā)出來的垦梆,這也是它們很多相似之處的原因之一匹颤。它們都是優(yōu)秀的異步事件驅(qū)動,NIO(非阻塞)網(wǎng)絡(luò)框架托猩,獲得了成百上千的商業(yè)項目驗證印蓖。當(dāng)然,在游戲開發(fā)中京腥,也是錦上添花赦肃。
Protobuf 是谷歌的Protocol Buffers的簡稱,它是一個輕便高效存儲和讀取結(jié)構(gòu)化數(shù)據(jù)的工具公浪,用于結(jié)構(gòu)化數(shù)據(jù)和字節(jié)碼之間互相轉(zhuǎn)換(序列化他宛、反序列化),非常適合用于網(wǎng)絡(luò)傳輸欠气,且支持多種編程語言厅各。游戲中使用Protobuf可以減少協(xié)議聯(lián)調(diào)出錯的幾率,因為一旦使用Protobuf作為協(xié)議工具预柒,當(dāng)定好協(xié)議后队塘,開發(fā)人員只管設(shè)置相應(yīng)字段值即可袁梗,如果擔(dān)心字段漏傳,就用required限定即可憔古;但是如果用其他的協(xié)議工具遮怜,那么開發(fā)人員除了設(shè)置相應(yīng)字段值外,可能還需要正確設(shè)置字段長度鸿市,及字段順序锯梁。
Netty的 ByteBuf 和 Mina的 IoBuffer 都是對JDK的 ByteBuffer 的高級封裝,比起JDK的ByteBuffer焰情,它們都支持動態(tài)擴展(而擴展JDK的ByteBuffer只能新建復(fù)制)涝桅,且修復(fù)了JDK的眾多NIO bug(比自己用原生JDK NIO API更少bug);但是 Mina的IoBuffer和JDK的ByteBuffer都只有一個標(biāo)識位置的指針position烙样,切換讀寫的時候要自己手動調(diào)用flip()和rewind()等冯遂,用起來稍微麻煩,而Netty的ByteBuf使用readerIndex和writerIndex分別維護讀操作和寫操作谒获,實現(xiàn)讀寫索引分離蛤肌,更加直觀。
本文假設(shè)讀者已對Netty有初步的了解批狱,如知道Netty的大致框架(后續(xù)可能會補一些Netty的介紹文章)裸准,了解Netty的核心組件的關(guān)系及其作用,了解消息處理的大致流程赔硫。下面放圖兩張供讀者回憶Netty基本知識點(摘自《Netty實戰(zhàn)》)炒俱,其余需更詳細了解Netty的建議細讀何品翻譯的《Netty實戰(zhàn)》和李林鋒的《Netty權(quán)威指南》,更細節(jié)的需要讀者自己鉆研了爪膊。
Netty核心組件的關(guān)系如下:【
- 一個 EventLoopGroup 包含一個或者多個 EventLoop权悟;
- 一個 EventLoop 在它的生命周期內(nèi)只和一個 Thread 綁定;
- 所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理推盛;
- 一個 Channel 在它的生命周期內(nèi)只注冊于一個 EventLoop峦阁;
- 一個 EventLoop 可能會被分配給一個或多個 Channel。
注意耘成,在這種設(shè)計中榔昔,一個給定 Channel 的 I/O 操作都是由相同的 Thread 執(zhí)行的,實際
上消除了對于同步的需要瘪菌∪龌幔】
注意上面【】中引用《Netty實戰(zhàn)》的最后一句,即“一個給定 Channel 的 I/O 操作都是由相同的 Thread 執(zhí)行的师妙,實際
上消除了對于同步的需要诵肛。”疆栏,雖然Netty中是這樣曾掂,但在實際游戲開發(fā)中,因為一個EventLoop管理多個Channel壁顶,而每個EventLoop上所有的I/O卻只由一個Thread處理珠洗,這就相當(dāng)于多個客戶端的I/O請求都由一個Thread處理了,這明顯同一時刻可能阻塞其他客戶端了若专,所以在實際游戲開發(fā)中许蓖,每個Channel的I/O操作(即每條協(xié)議請求),都會再放到一個邏輯線程池中處理调衰,這樣避免阻塞其他客戶端膊爪,哎呀,一不小心劇透后面游戲框架博文的內(nèi)容了嚎莉,具體詳情請見后續(xù)框架解讀吧米酬。
以上是I/O操作的流程示意圖,實際中ChannelPipeline中的入站和出站handler都是類似如下添加的
bootstrap.handler(new ChannelInitializer<ServerSocketChannel>() {
@Override
protected void initChannel(ServerSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("http_codec", new HttpClientCodec());
pipeline.addLast("http_aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("protobuf_decoder", new ProtoDecoder(null, 5120));
pipeline.addLast("server_handler", new ProtoServerHandler());
pipeline.addLast("protobuf_encoder", new ProtoEncoder(checkSum, 2048));
}
});
本文的原來標(biāo)題是《Netty+Protobuf實現(xiàn)游戲的TCP通信》趋箩,本來是想一篇文章即介紹完這個話題的赃额,但發(fā)現(xiàn)因為想引入Netty和Protobuf的簡介導(dǎo)致文章太長了,不簡介又擔(dān)心新手讀者沒有個承上啟下的因果關(guān)系導(dǎo)致強行去理解Netty和Protobuf實現(xiàn)TCP通信顯得略為僵硬叫确,所以本文標(biāo)題還是改為《游戲之網(wǎng)絡(luò)進階》算了跳芳,旨在對游戲中使用Netty和Protobuf有個大致的介紹,而實際上Netty的內(nèi)容還遠不止本文剛說的那一丁點竹勉,想想李林鋒大神那兩本Netty的書就知道了飞盆,希望讀者能自己去鉆研細讀,廣思集益次乓,從而對游戲的網(wǎng)絡(luò)框架有更好的了解吓歇。
通常,了解一個大眾使用的框架的規(guī)則票腰,比自己手寫類似功能更為迅捷和靠譜照瘾,在現(xiàn)在快節(jié)奏的游戲更新迭代中,快速開發(fā)一款游戲成為節(jié)約成本丧慈,取得市場先手優(yōu)勢的先決條件析命。但這并不鼓勵只是一味的“拿來主義”而不推崇自己的主觀動手能力了,實際的技術(shù)沉淀還需我們自己不斷拓展加深逃默,說不定哪天一個很好的框架就出自這樣動手能力極強的讀者手中了鹃愤。下篇博文可真正進入《Netty+Protobuf實現(xiàn)游戲的TCP通信》了,敬請閱讀完域。