首页 > 代码库 > Netty Protobuf C# 通信

Netty Protobuf 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 -> 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的数据需要做处理