標籤:java netty c# 通訊
1、目的
最近一個遊戲開發需要使用Netty 和 Protobuf,之前使用的thift(https://thrift.apache.org/),然後找了一下Netty、protobuf的開發資料,網上千篇一律的都是照搬官方的LocaleTime Demo。我只能說Demo就是Demo,在真正遊戲開發中能用嗎? 在這裡,我把自己實現的、測試通過的、有效方式提供給大家,包括原始碼,提供給開始嘗試使用Netty 和 protobuf技術,但是卻有點迷糊的同學。當然如果你是高手,也可以最佳化和提出改造的點,歡迎來噴。
2、簡介
http://netty.io/ netty 官方
http://download.csdn.net/detail/zeus_9i/7648115 【netty in action 英文版】
https://code.google.com/p/protobuf/ 什麼是 protobuf 這裡略過
http://protobuf-dt.googlecode.com/git/update-site/ proto 檔案 eclipse 編輯器很方便,但是不一定能開啟,天朝給強了。
3、關鍵詞
協議格式
協議格式一般都是 TLV(type, length, value),但是這篇文章中是: LTV (length, type, value) 曆史遺留問題,不影響通訊。
Decoder 把位元據變成對象的過程
Encoder 把對象變成位元據的過程
4、實現
怎麼組織一個 ServerBootstrap 和 ClientBootstrap 這裡就不詳細說明了。
主要說兩點:1、decoder、encoder,
2、協議分發,也就是decoder以後怎麼把協議號匹配到對應的業務處理邏輯
核心代碼清單
class MessageMappingManager 映射,儲存協議號與 MessageLite對應關係,雙向,id -> class, class -> idclass GameMessageDecoder 解碼,這裡會使用 MessageMappingManager 中的對應關係,來尋找具體的proto解碼類(MessageLite)class ProtobufCommonDecoder extends ProtobufDecoder 這個是使用了Netty 內建的Protobuf 資料體解碼類,自己去看代碼class GameMessageEncoder 編碼,同上,並且把資料最後整理成:lenght + type + value 的二進位格式
眼尖的人應該會發現,這裡沒有:ProtobufVarint32FrameDecoder 和 ProtobufVarint32LengthFiledPrepender 官方的Demo 裡面都使用到了。這裡我們不需要使用者兩個類。
解碼代碼
public class ProtobufCommonDecoder extends ProtobufDecoder {public ProtobufCommonDecoder(MessageLite prototype) {super(prototype);}public MessageLite invokeDecode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {return (MessageLite) decode(ctx, channel, msg);}}/** * 協議ID映射管理 * 自動組建檔案,切勿修改 */public class MessageMappingManager {/** msgId <-> MessageLite Req請求映射 */private Map<Integer, MessageLite> idClazzMap;/** MessageLiteClass <--> msgId Resp響應映射 */private Map<Class<? extends MessageLite>, Integer> clazzIdMap;public void init() {idClazzMap = new HashMap<Integer, MessageLite>();clazzIdMap = new HashMap<Class<? extends MessageLite>, Integer>();idClazzMap.put(2, ErrorNoticeResp.getDefaultInstance());clazzIdMap.put(ErrorNoticeResp.class, 2);idClazzMap.put(4, EnterSceneResp.getDefaultInstance());clazzIdMap.put(EnterSceneResp.class, 4);}public MessageLite getMessage(int messageId) {return idClazzMap.get(messageId);}public int getMessageId(Class<?> clazz) {return clazzIdMap.get(clazz);}}public class GameMessageDecoder extends OneToOneDecoder {public static final Log LOG = Loggers.message;@Overrideprotected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { if (!(msg instanceof ChannelBuffer)) { return msg; } ChannelBuffer buf = (ChannelBuffer) msg;if( !buf.readable() ) {return null;}buf.markReaderIndex(); int messageId = buf.readShort();if( LOG.isDebugEnabled() ) {LOG.debug("receive messageId: " + messageId);}MessageMappingManager mappingManager = Application.getBean(MessageMappingManager.class);MessageLite bodyLite = mappingManager.getMessage(messageId);if(bodyLite == null) {buf.resetReaderIndex();LOG.error("Can't find proto message body decoder. messageId : " + messageId);return null;}ProtobufCommonDecoder decoder = new ProtobufCommonDecoder( mappingManager.getMessage(messageId) );MessageLite dataLite = decoder.invokeDecode(ctx, channel, buf);GameMessage message = new GameMessage();message.setId(messageId);message.setMessage(dataLite);return message;}}
GameMessage 是自訂對象,裡面儲存messageId + MessageLite,到這裡就可以解碼出訊息對象了
注意:網路傳輸使用的 大端位元組學,Java本身預設就是大端所以不需要處理,C#預設小端,所以C#發往Java的資料需要做處理