简介
netty中有很多适配不同协定的编码工具,对于风行的google出品的protobuf也不例外。netty为其提供了ProtobufDecoder和ProtobufEncoder两个工具还有对应的frame detection,接下来咱们会通过一个例子来具体解说如何在netty中应用protobuf。
定义protobuf
咱们举个最简略的例子,首先定义一个须要在网络中进行传输的message,这里咱们定义一个student对象,他有一个age和一个name属性,如下所示:
syntax = "proto3";package com.flydean17.protobuf;option java_multiple_files = true;option java_package = "com.flydean17.protobuf";option java_outer_classname = "StudentWrapper";message Student { optional int32 age = 1; optional string name =2;}
应用上面的命令,对其进行编译:
protoc --experimental_allow_proto3_optional -I=. --java_out=. student.proto
能够看到生成了3个文件,别离是Student,StudentOrBuilder和StudentWrapper。其中Student和StudentOrBuilder是咱们真正须要用到的对象。
定义handler
在handler中,咱们次要进行对音讯进行解决,这里咱们在clientHandler中进行音讯的构建和发送,StudentClientHandler继承SimpleChannelInboundHandler并从新channelActive办法, 在该办法中咱们应用protobuf的语法,构建一个新的Student实例,并给他设置好age和name两个属性。
而后应用ctx.write和ctx.flush办法将其发送到server端:
public void channelActive(ChannelHandlerContext ctx) throws Exception { // channel沉闷 //构建一个Student,并将其写入到channel中 Student student= Student.newBuilder().setAge(22).setName("flydean").build(); log.info("client发送音讯{}",student); ctx.write(student); ctx.flush(); }
StudentServerHandler也是继承SimpleChannelInboundHandler,并重写channelRead0办法,当server端读取到student音讯的时候,日志输入,并将其回写到channel中,供clientHandler读取:
public void channelRead0(ChannelHandlerContext ctx, Student student) throws Exception { log.info("server收到音讯{}",student); // 写入音讯 ChannelFuture future = ctx.write(student); }
当client读取到音讯之后,间接日志输入,不再做进一步解决,到此,一轮client和server端的交互就实现了:
public void channelRead0(ChannelHandlerContext ctx, Student student) throws Exception { log.info("client收到音讯{}",student); }
设置ChannelPipeline
在上一节,不论是在StudentClientHandler还是在StudentServerHandler中,咱们都假如channel中传递的对象就是Student,而不是原始的ByteBuf。这是怎么做到的呢?
这里咱们须要应用到netty提供的frame detection,netty为protobuf协定专门提供了ProtobufDecoder和ProtobufEncoder,用于对protobuf对象进行编码和解码。
然而这两个编码和解码器别离是MessageToMessageEncoder和MessageToMessageDecoder,他们是音讯到音讯的编码和解码器,所以还须要和frame detection配合应用。
netty同样提供了和protobuf配合应用的frame detector,他们是ProtobufVarint32FrameDecoder和ProtobufVarint32LengthFieldPrepender。
Varint32指的是protobuf的编码格局,第一个字节应用的是可变的varint。
有了frame detector和编码解码器,咱们只须要将其程序退出ChannelPipeline即可。
在客户端,StudentClientInitializer继承自ChannelInitializer,咱们须要重写其initChannel办法:
public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(new ProtobufVarint32FrameDecoder()); p.addLast(new ProtobufDecoder(Student.getDefaultInstance())); p.addLast(new ProtobufVarint32LengthFieldPrepender()); p.addLast(new ProtobufEncoder()); p.addLast(new StudentClientHandler()); }
在服务器端,同样StudentServerInitializer也继承自ChannelInitializer,也须要重写其initChannel办法:
public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new ProtobufVarint32FrameDecoder()); p.addLast(new ProtobufDecoder(Student.getDefaultInstance())); p.addLast(new ProtobufVarint32LengthFieldPrepender()); p.addLast(new ProtobufEncoder()); p.addLast(new StudentServerHandler()); }
这样ChannelPipeline也设置实现了。
构建client和server端并运行
最初好做的就是构建client和server端并运行,这和一般的netty客户端和服务器端并没有什么区别:
构建StudentClient:
public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new StudentClientInitializer()); // 建设连贯 Channel ch = b.connect(HOST, PORT).sync().channel(); // 期待敞开 ch.closeFuture().sync(); } finally { group.shutdownGracefully(); } }
构建StudentServer:
public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new StudentServerInitializer()); b.bind(PORT).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
运行可得:
server端:[nioEventLoopGroup-3-1] INFO c.f.protobuf.StudentServerHandler - server收到音讯age: 22name: "flydean"[nioEventLoopGroup-2-1] INFO c.f.protobuf.StudentClientHandler - client发送音讯age: 22name: "flydean"client端:[nioEventLoopGroup-2-1] INFO c.f.protobuf.StudentClientHandler - client收到音讯age: 22name: "flydean"
可见Student音讯曾经发送和接管胜利了。
总结
netty提供了很多和协定适配的工具类,这样咱们就能够专一于业务逻辑,不须要思考具体的编码转换的问题,十分好用。
本文的例子能够参考:learn-netty4
本文已收录于 http://www.flydean.com/17-netty-protobuf/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!