Netty入门三:Netty的Protobuf服务端和客户端开发

文章目录

  • Google Protobuf概述
  • Netty的Protobuf服务端demo
  • Netty的Protobuf客户端demo

Google Protobuf概述

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

  • 和其他类似技术的比较

    和 XML,JSON,Thrift 等等相比,Protobuf 有什么不同呢?

    简单说来 Protobuf 的主要优点就是:简单,快。

  • Protobuf 的优点

    Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。

    它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。

    Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。

    使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。

  • 适用场景

  1. 传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景,如 即时IM (QQ、微信)的需求场景

  2. 在 传输数据量较大的需求场景下,Protocol Buffer比XML、Json 更小、更快、使用 & 维护更简单!

  • 安装教程
    https://blog.csdn.net/xxjuanq_only_one/article/details/50465272

  • 从proto文件转换为Java 文件

    protoc --java_out=./java/ ./proto/helloworld.proto
    

    protoc 的命令格式为 protoc [OPTION] PROTO_FILES (最后是待编译的 proto文件)
    –java_out 为输出java代码的目录,这里指定的是 ./java/ 目录。
    随后我们指定了proto文件的位置 ./proto/helloworld.proto 。
    执行上述命令,我们就 ./java/ 目录下就产生了对应的 java文件。

  • 相关简明教程如下
    Protocol Buffers简明教程
    Intellij IDEA中使用Protobuf的正确姿势

Netty的Protobuf服务端demo

场景:服务端接收客户端的用户订购请求消息,返回应答消息,定义如下:

syntax = "proto3"; // 声明为protobuf 3定义文件
package netty;

option java_package = "netty.protobuf.pojo"; // 声明生成消息类的java包路径
option java_outer_classname = "SubscribeReqProto"; // 声明生成消息类的类名

message SubscribeReq {

    int32 subReqID = 1;
    string userName = 2;
    string productName = 3;
    repeated string address = 4;

}


syntax = "proto3"; // 声明为protobuf 3定义文件
package netty;

option java_package = "netty.protobuf.pojo"; // 声明生成消息类的java包路径
option java_outer_classname = "SubscribeRespProto"; // 声明生成消息类的类名

message SubscribeResp {

    int32 subReqID = 1;
    int32 respCode = 2;
    string desc = 3;
}

利用以下命令生成java文件,拷贝到工程文件中

protoc --java_out=. ./netty/protobuf/SubscribeRespProto.proto

如图
Netty入门三:Netty的Protobuf服务端和客户端开发_第1张图片
服务端代码如下:

public class SubReqServer {
  public void bind(int port) throws Exception {
    // 配置服务端的NIO线程组
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .option(ChannelOption.SO_BACKLOG, 100)
          .handler(new LoggingHandler(LogLevel.INFO))
          .childHandler(
              new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                  ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                  ch.pipeline()
                      .addLast(
                          new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
                  ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                  ch.pipeline().addLast(new ProtobufEncoder());
                  ch.pipeline().addLast(new SubReqServerHandler());
                }
              });

      // 绑定端口,同步等待成功
      ChannelFuture f = b.bind(port).sync();

      // 等待服务端监听端口关闭
      f.channel().closeFuture().sync();
    } finally {
      // 优雅退出,释放线程池资源
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }

  public static void main(String[] args) throws Exception {
    int port = 8080;
    if (args != null && args.length > 0) {
      try {
        port = Integer.valueOf(args[0]);
      } catch (NumberFormatException e) {
        // 采用默认值
      }
    }
    new SubReqServer().bind(port);
  }
}

public class SubReqServerHandler extends ChannelInboundHandlerAdapter {

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    SubscribeReqProto.SubscribeReq req = (SubscribeReqProto.SubscribeReq) msg;
    if ("Lilinfeng".equalsIgnoreCase(req.getUserName())) {
      System.out.println("Service accept client subscribe req : [" + req.toString() + "]");
      ctx.writeAndFlush(resp(req.getSubReqID()));
    }
  }

  private SubscribeRespProto.SubscribeResp resp(int subReqID) {
    SubscribeRespProto.SubscribeResp.Builder builder =
        SubscribeRespProto.SubscribeResp.newBuilder();
    builder.setSubReqID(subReqID);
    builder.setRespCode(0);
    builder.setDesc("Netty book order succeed, 3 days later, sent to the designated address");
    return builder.build();
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    cause.printStackTrace();
    ctx.close(); // 发生异常,关闭链路
  }
}

Netty的Protobuf客户端demo

public class SubReqClient {

  public void connect(int port, String host) throws Exception {
    // 配置客户端NIO线程组
    EventLoopGroup group = new NioEventLoopGroup();
    try {
      Bootstrap b = new Bootstrap();
      b.group(group)
          .channel(NioSocketChannel.class)
          .option(ChannelOption.TCP_NODELAY, true)
          .handler(
              new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                  ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                  ch.pipeline()
                      .addLast(
                          new ProtobufDecoder(
                              SubscribeRespProto.SubscribeResp.getDefaultInstance()));
                  ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                  ch.pipeline().addLast(new ProtobufEncoder());
                  ch.pipeline().addLast(new SubReqClientHandler());
                }
              });

      // 发起异步连接操作
      ChannelFuture f = b.connect(host, port).sync();

      // 当代客户端链路关闭
      f.channel().closeFuture().sync();
    } finally {
      // 优雅退出,释放NIO线程组
      group.shutdownGracefully();
    }
  }

  /**
   * @param args
   * @throws Exception
   */
  public static void main(String[] args) throws Exception {
    int port = 8080;
    if (args != null && args.length > 0) {
      try {
        port = Integer.valueOf(args[0]);
      } catch (NumberFormatException e) {
        // 采用默认值
      }
    }
    new SubReqClient().connect(port, "127.0.0.1");
  }
}

public class SubReqClientHandler extends ChannelInboundHandlerAdapter {

  /** Creates a client-side handler. */
  public SubReqClientHandler() {}

  @Override
  public void channelActive(ChannelHandlerContext ctx) {
    for (int i = 0; i < 10; i++) {
      ctx.write(subReq(i));
    }
    ctx.flush();
  }

  private SubscribeReqProto.SubscribeReq subReq(int i) {
    SubscribeReqProto.SubscribeReq.Builder builder = SubscribeReqProto.SubscribeReq.newBuilder();
    builder.setSubReqID(i);
    builder.setUserName("Lilinfeng");
    builder.setProductName("Netty Book For Protobuf");
    List<String> address = new ArrayList<>();
    address.add("NanJing YuHuaTai");
    address.add("BeiJing LiuLiChang");
    address.add("ShenZhen HongShuLin");
    builder.addAllAddress(address);
    return builder.build();
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    System.out.println("Receive server response : [" + msg + "]");
  }

  @Override
  public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    ctx.flush();
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    cause.printStackTrace();
    ctx.close();
  }
}

运行结果:

  • 服务端结果:
Service accept client subscribe req : [userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 1
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 2
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 3
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 4
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 5
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 6
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 7
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 8
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
Service accept client subscribe req : [subReqID: 9
userName: "Lilinfeng"
productName: "Netty Book For Protobuf"
address: "NanJing YuHuaTai"
address: "BeiJing LiuLiChang"
address: "ShenZhen HongShuLin"
]
  • 客户端结果:
Receive server response : [desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 1
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 2
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 3
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 4
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 5
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 6
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 7
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 8
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]
Receive server response : [subReqID: 9
desc: "Netty book order succeed, 3 days later, sent to the designated address"
]

参考文章:
《netty权威指南2》

你可能感兴趣的:(Netty)