从零开始学netty——初见protobuf

在看此篇内容时需要浏览下面内容
从零开始学netty——如何面对粘包和拆包
从零开始学netty——自定义协议

netty和protobuf的关系

netty和protobuf其实没啥关系。protobuf主要是做对象序列化的,他是一个高效的对象转字节,字节转对象的协议,并且支持多种语言。查他的好处,网上能查一大堆。以前说过自定义协议,传输的byte不总能是string的吧,传输对象肯定更方便编程啊,protobuf就提供了这样的方便功能。

protobuf简易使用教程

首先是写一个.proto的文件内容如下。

syntax = "proto3";#声明版本,3支持map,2不支持
message CommonMessage{
#message对应的是类
	 int64 id =1;
	 repeated  string list=2 ;//list
}

message MapMessage{
	 map<string, CommonMessage> mapInfo=1;#map

}
option java_package = "com.xp";
option java_multiple_files = true;#生成的类是多个的。

上面的例子基本把所有的情况都列举了,使用list用repeated标识类型,使用map用map关键字。如果引用的也是protobuf生成的类就直接可以写那个类的message的名字。

其他常见基本类型参考https://developers.google.com/protocol-buffers/docs/proto3,里面有每种类型对应的java,c++等类型。

option java_multiple_files 我建议使用这个参数,否则他会把对象生成在一个类里,然后用具体的类都是类名.类名的用法。

使用命令生成对象

protoc --java_out=./src info.proto

使用protobuf开发程序

public void connect() throws InterruptedException{
		EventLoopGroup worker =new NioEventLoopGroup();
		try{
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(worker).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {

				@Override
				protected void initChannel(Channel channel) throws Exception {
					channel.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
					channel.pipeline().addLast(new ProtobufEncoder());
					
				}
			});
			
			ChannelFuture connect = bootstrap.connect("127.0.0.1", 1900);
			CommonMessage.Builder commonMessageBuilder= CommonMessage.newBuilder();
			commonMessageBuilder.addList("he");
			commonMessageBuilder.addList("he");
			commonMessageBuilder.setId(66);
			MapMessage.Builder mapMessageBuilder = MapMessage.newBuilder();
			mapMessageBuilder.putMapInfo("xx", commonMessageBuilder.build());
			MapMessage build = mapMessageBuilder.build();
			connect.channel().writeAndFlush(build);
			System.out.println("over");
			connect.sync();
		}finally{
			worker.shutdownGracefully();
		}
		

protobuf都是先建立builder对象,设置完以后,build一下产生message对象。剩下的操作就是普通的java对象。

都使用protobuf生成对象了,序列化和反序列化,都是依靠他来做了,这里就需要ProtobufEncoder。

大家应该还记得粘包和拆包操作吧。光有byte是不够的,服务端不知道读取多少,ProtobufVarint32LengthFieldPrepender会加一个32位的标识(int)表示数据包的长度。

public void bind(int port) throws InterruptedException {
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup worker = new NioEventLoopGroup();
		ServerBootstrap bootstrap = new ServerBootstrap();
		bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
				.childHandler(new ChannelInitializer<Channel>() {

					@Override
					protected void initChannel(Channel channel) throws Exception {
						channel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
						channel.pipeline().addLast(new ProtobufDecoder(MapMessage.getDefaultInstance()));
						channel.pipeline().addLast(new SimpleChannelInboundHandler<MapMessage>() {

							@Override
							protected void channelRead0(ChannelHandlerContext arg0, MapMessage arg1) throws Exception {
								System.out.println(arg1.getMapInfoMap().get("xx"));

							}
							@Override
							public void channelActive(ChannelHandlerContext ctx) throws Exception {
								System.out.println("connection");
								super.channelActive(ctx);
							}
							@Override
							public void channelInactive(ChannelHandlerContext ctx) throws Exception {
								System.out.println("close");
								super.channelInactive(ctx);
							}
							
						});

					}
				});

		ChannelFuture sync = bootstrap.bind(port).sync();
		sync.channel().closeFuture().sync();

服务器端这里也需要有对应的解码器,ProtobufVarint32FrameDecoder就是对应ProtobufVarint32LengthFieldPrepender的解码器。ProtobufDecoder需要说明解出来的对象是什么,MapMessage.getDefaultInstance()就是说明了这个问题,解码出来的对象是什么类型的。

自定义协议的好处

刚才的例子大家也明白了,已经有很多写好的编码器或者解码器了,并且还处理了粘包和拆包的问题。是不是现在可以用一个对象来表示协议呢。 我的回答是否定的,虽然这样开发比较省事,也不用自己管理粘包和拆包,但是会造成编码逻辑的问题。

  1. 数据内容和协议内容混搭。你的对象里既包含协议,又包含内容。不符合面向对象的单一职责设计,正常的自定义协议基本包含这样的信息,这个操作是干什么的,还有这个操作的数据是什么,起码是两部分的,是要做到操作和数据分开的。数据传输数据,内容传输内容,虽然可以写在一个解码器里,在确定调用模块后,只传递数据走就好。
  2. 魔数校验。有的协议是需要有魔数的,例如java的class文件都是0xCAFEBABE开头,用做校验,如果读取不通过会略过一些字节直到找到魔数。
  3. 可移植性差。protobuf不是唯一的选择,当出现更好的协议想替换的时候就很麻烦,如果单单是使用他的序列化和反序列化的功能替换很简单。只把序列化的地方改改就好。

猜你喜欢

转载自my.oschina.net/xpbob/blog/1812575