写点什么

netty 系列之: 在 netty 中使用 protobuf 协议

发布于: 5 小时前
netty系列之:在netty中使用protobuf协议

简介 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/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 5 小时前阅读数: 6
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:在netty中使用protobuf协议