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

Dear 丶 2022-09-08 00:30 360阅读 0赞

文章目录

  • 简介
  • 定义protobuf
  • 定义handler
  • 设置ChannelPipeline
  • 构建client和server端并运行
  • 总结

简介

netty中有很多适配不同协议的编码工具,对于流行的google出品的protobuf也不例外。netty为其提供了ProtobufDecoder和ProtobufEncoder两个工具还有对应的frame detection,接下来我们会通过一个例子来详细讲解如何在netty中使用protobuf。

定义protobuf

我们举个最简单的例子,首先定义一个需要在网络中进行传输的message,这里我们定义一个student对象,他有一个age和一个name属性,如下所示:

  1. syntax = "proto3";
  2. package com.flydean17.protobuf;
  3. option java_multiple_files = true;
  4. option java_package = "com.flydean17.protobuf";
  5. option java_outer_classname = "StudentWrapper";
  6. message Student {
  7. optional int32 age = 1;
  8. optional string name =2;
  9. }

使用下面的命令,对其进行编译:

  1. 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端:

  1. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  2. // channel活跃
  3. //构建一个Student,并将其写入到channel中
  4. Student student= Student.newBuilder().setAge(22).setName("flydean").build();
  5. log.info("client发送消息{}",student);
  6. ctx.write(student);
  7. ctx.flush();
  8. }

StudentServerHandler也是继承SimpleChannelInboundHandler,并重写channelRead0方法,当server端读取到student消息的时候,日志输出,并将其回写到channel中,供clientHandler读取:

  1. public void channelRead0(ChannelHandlerContext ctx, Student student) throws Exception {
  2. log.info("server收到消息{}",student);
  3. // 写入消息
  4. ChannelFuture future = ctx.write(student);
  5. }

当client读取到消息之后,直接日志输出,不再做进一步处理,到此,一轮client和server端的交互就完成了:

  1. public void channelRead0(ChannelHandlerContext ctx, Student student) throws Exception {
  2. log.info("client收到消息{}",student);
  3. }

设置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方法:

  1. public void initChannel(SocketChannel ch) {
  2. ChannelPipeline p = ch.pipeline();
  3. p.addLast(new ProtobufVarint32FrameDecoder());
  4. p.addLast(new ProtobufDecoder(Student.getDefaultInstance()));
  5. p.addLast(new ProtobufVarint32LengthFieldPrepender());
  6. p.addLast(new ProtobufEncoder());
  7. p.addLast(new StudentClientHandler());
  8. }

在服务器端,同样StudentServerInitializer也继承自ChannelInitializer,也需要重写其initChannel方法:

  1. public void initChannel(SocketChannel ch) throws Exception {
  2. ChannelPipeline p = ch.pipeline();
  3. p.addLast(new ProtobufVarint32FrameDecoder());
  4. p.addLast(new ProtobufDecoder(Student.getDefaultInstance()));
  5. p.addLast(new ProtobufVarint32LengthFieldPrepender());
  6. p.addLast(new ProtobufEncoder());
  7. p.addLast(new StudentServerHandler());
  8. }

这样ChannelPipeline也设置完成了。

构建client和server端并运行

最后好做的就是构建client和server端并运行,这和普通的netty客户端和服务器端并没有什么区别:

构建StudentClient:

  1. public static void main(String[] args) throws Exception {
  2. EventLoopGroup group = new NioEventLoopGroup();
  3. try {
  4. Bootstrap b = new Bootstrap();
  5. b.group(group)
  6. .channel(NioSocketChannel.class)
  7. .handler(new StudentClientInitializer());
  8. // 建立连接
  9. Channel ch = b.connect(HOST, PORT).sync().channel();
  10. // 等待关闭
  11. ch.closeFuture().sync();
  12. } finally {
  13. group.shutdownGracefully();
  14. }
  15. }

构建StudentServer:

  1. public static void main(String[] args) throws Exception {
  2. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  3. EventLoopGroup workerGroup = new NioEventLoopGroup();
  4. try {
  5. ServerBootstrap b = new ServerBootstrap();
  6. b.group(bossGroup, workerGroup)
  7. .channel(NioServerSocketChannel.class)
  8. .handler(new LoggingHandler(LogLevel.INFO))
  9. .childHandler(new StudentServerInitializer());
  10. b.bind(PORT).sync().channel().closeFuture().sync();
  11. } finally {
  12. bossGroup.shutdownGracefully();
  13. workerGroup.shutdownGracefully();
  14. }
  15. }

运行可得:

  1. server端:
  2. [nioEventLoopGroup-3-1] INFO c.f.protobuf.StudentServerHandler - server收到消息age: 22
  3. name: "flydean"
  4. [nioEventLoopGroup-2-1] INFO c.f.protobuf.StudentClientHandler - client发送消息age: 22
  5. name: "flydean"
  6. client端:
  7. [nioEventLoopGroup-2-1] INFO c.f.protobuf.StudentClientHandler - client收到消息age: 22
  8. name: "flydean"

可见Student消息已经发送和接收成功了。

总结

netty提供了很多和协议适配的工具类,这样我们就可以专注于业务逻辑,不需要考虑具体的编码转换的问题,非常好用。

本文的例子可以参考:learn-netty4

本文已收录于 http://www.flydean.com/17-netty-protobuf/

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

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

发表评论

表情:
评论列表 (有 0 条评论,360人围观)

还没有评论,来说两句吧...

相关阅读