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

42次阅读

共计 4293 个字符,预计需要花费 11 分钟才能阅读完成。

简介

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: 22
name: "flydean"

[nioEventLoopGroup-2-1] INFO  c.f.protobuf.StudentClientHandler - client 发送音讯 age: 22
name: "flydean"

client 端:[nioEventLoopGroup-2-1] INFO  c.f.protobuf.StudentClientHandler - client 收到音讯 age: 22
name: "flydean"

可见 Student 音讯曾经发送和接管胜利了。

总结

netty 提供了很多和协定适配的工具类,这样咱们就能够专一于业务逻辑,不须要思考具体的编码转换的问题,十分好用。

本文的例子能够参考:learn-netty4

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

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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

正文完
 0