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 -> id
class 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;
@Override
protected 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的数据需要做处理