关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

Netty Protobuf C# 通信

发布时间:2019-12-05 17:06:49

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



/template/Home/Zkeys/PC/Static