(卷七)Google ProtoBuf

167 阅读6分钟

7.1 编码和解码的基本介绍

  1. 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据 时就需要解码

  2. codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节 码数据,decoder 负责把字节码数据转换成业务数据

7.2 Netty 本身的编码解码的机制和问题分析

  1. Netty 自身提供了一些 codec(编解码器)

  2. Netty 提供的编码器 StringEncoder,对字符串数据进行编码 ObjectEncoder,对 Java 对象进行编码 ...

  3. Netty 提供的解码器 StringDecoder, 对字符串数据进行解码 ObjectDecoder,对 Java 对象进行解码 ...

  4. Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码 底层使用的仍是 Java 序列化技术 , 而 Java 序列化技术本身效率就不高,存在如下问题 无法跨语言 序列化后的体积太大,是二进制编码的 5 倍多。 序列化性能太低

  5. => 引出 新的解决方案 [Google 的 Protobuf]

7.3 Protobuf

  1. Protobuf 基本介绍和使用示意图

  2. Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式, 可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式 。 目前很多公司 http+json tcp+protobuf

  3. 参考文档 : developers.google.com/protocol-bu… 语言指南

  4. Protobuf 是以 message 的方式来管理数据的.

  5. 支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言,例如 C++、 C#、Java、python 等)

  6. 高性能,高可靠性

  7. 使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述。说明,在 idea 中编 写 .proto 文件时,会自动提示是否下载 .ptotot 编写插件. 可以让语法高亮。

  8. 然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件

7.4 Protobuf 快速入门实例 编写程序,使用 Protobuf 完成如下功能

  1. 客户端可以发送一个 Student PoJo 对象到服务器 (通过 Protobuf 编码)

  2. 服务端能接收 Student PoJo 对象,并显示信息(通过 Protobuf 解码)

7.4.1 相关环境搭建

  1. 下载编译器 github.com/protocolbuf…

我们下载的是3.6.1的版本 github.com/protocolbuf…

  1. 解压目录,加入到环境变量中

  2. 测试是否配置成功

  3. 编写文件 student.proto

syntax = "proto3"; //版本 
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名 

//protobuf 使用 message 管理数据 
//会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的 POJO 对象 
message Student { 
	// Student 类中有 一个属性 名字为 id 类型为 int32(protobuf 类型) 1 表示属性序号,不是值 
        int32 id = 1; 
        string name = 2;
    }
  1. 定位到文件夹目录,编译文件
E:\develop\protoc-3.6.1-win32\bin\protoc.exe --java_out=. Student.proto

  1. pom文件引入 protobuf坐标
<dependencies>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.6.1</version>
    </dependency>
</dependencies>

7.4.2 使用potoBuf进行数据传输

NettyServer

import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();

        bootstrap.group(bossGroup, workGroup) //Netty服务端有两个工作组,这里得设置上
                .channel(NioServerSocketChannel.class) //当前的通道用 NioServerSocketChanel作为服务器的实现
                .option(ChannelOption.SO_BACKLOG, 128)// 设置线程队列得到连接个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() { //创建一个通道初始化对象
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                        pipeline.addLast(new NettyServerHandler());
                    }
                });

        System.out.println("服务器 is ready...");
        ChannelFuture cf = bootstrap.bind(6668).sync();
        cf.channel().closeFuture().sync();
    }
}

NettyServerHandler

import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        System.out.println("服务端接收了消息");

        StudentPOJO.Student student = (StudentPOJO.Student) msg;

        System.out.println(student.getId()+" "+student.getName());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端已连接:"+ctx.channel().remoteAddress());
    }
}

NettyClient

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(group)//第一步就设置一个事件循环组
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() { //第二步,就是设置处理器
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new ProtobufEncoder());
                        pipeline.addLast(new NettyClientHandler());

                    }
                });

        ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();

        channelFuture.channel().closeFuture().sync();

    }
}

NettyClientHandler

import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪,就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(1).setName("rexli").build();

        System.out.println("已就绪,发送消息给服务端");
        ctx.writeAndFlush(student);

    }
}

使用protobuf遇到的坑

7.5 Protobuf 快速入门实例 2

  1. 客户端可以随机发送 Student PoJo/ Worker PoJo 对象到服务器 (通过 Protobuf 编码)

  2. 服务端能接收 Student PoJo/ Worker PoJo 对象(需要判断是哪种类型),并显示信息(通过 Protobuf 解码)

Student.proto

syntax = "proto3";
option optimize_for = SPEED; // 加快解析
option java_outer_classname="MyDataInfo"; // 外部类名, 文件名


//protobuf 可以使用 message 管理其他的 message
message MyMessage {

    //定义一个枚举类型
    enum DataType {
        StudentType = 0; //在 proto3 要求 enum 的编号从 0 开始
        WorkerType = 1;
    }

    //用 data_type 来标识传的是哪一个枚举类型
    DataType data_type = 1; //表示每次枚举类型最多只能出现其中的一个, 节省空间

    oneof dataBody {
        Student student = 2;
        Worker worker = 3;
    }
}

message Student {
    int32 id = 1;//Student 类的属性
    string name = 2; //
}

message Worker {
    string name=1;
    int32 age=2;
}

下面进行相关代码展示

NettyClient

这个类不需要做改变

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(group)//第一步就设置一个事件循环组
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() { //第二步,就是设置处理器
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new ProtobufEncoder());
                        pipeline.addLast(new NettyClientHandler());

                    }
                });

        ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();

        channelFuture.channel().closeFuture().sync();

    }
}

NettyClientHandler

在进行发送消息的时候,随机的发送Student对象,或者Worker对象

import com.atguigu.netty.groupChat.StudentPOJO;
import com.sun.nio.sctp.MessageInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Random;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪,就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        int random = new Random().nextInt(3);

        MyDataInfo.MyMessage myMessage=null;

        if(random==0){
            myMessage= MyDataInfo.MyMessage.newBuilder()
                    .setDataType(MyDataInfo.MyMessage.DataType.StudentType)
                    .setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("ABC"))
                    .build();
        }else{
            myMessage= MyDataInfo.MyMessage.newBuilder()
                    .setDataType(MyDataInfo.MyMessage.DataType.WorkerType)
                    .setWorker(MyDataInfo.Worker.newBuilder().setAge(55).setName("ABC"))
                    .build();
        }

        ctx.writeAndFlush(myMessage);
    }
}

NettyServer

在设置解码器的时候,要换一下类型 MyDataInfo.MyMessage.getDefaultInstance()


package com.atguigu.netty.protobuf02;

import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();

        bootstrap.group(bossGroup, workGroup) //Netty服务端有两个工作组,这里得设置上
                .channel(NioServerSocketChannel.class) //当前的通道用 NioServerSocketChanel作为服务器的实现
                .option(ChannelOption.SO_BACKLOG, 128)// 设置线程队列得到连接个数
                .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() { //创建一个通道初始化对象
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {

                        ChannelPipeline pipeline = ch.pipeline();
                        //pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                        pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
                        pipeline.addLast(new NettyServerHandler());
                    }
                });

        System.out.println("服务器 is ready...");
        ChannelFuture cf = bootstrap.bind(6668).sync();
        cf.channel().closeFuture().sync();
    }
}

NettyServerHandler

客户端在进行接收时,也要根据消息的类型进行判断, 然后用来展示分别发送什么消息

package com.atguigu.netty.protobuf02;

import com.atguigu.netty.groupChat.StudentPOJO;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        System.out.println("服务端接收了消息");

        MyDataInfo.MyMessage myMessage = (MyDataInfo.MyMessage) msg;

        if(myMessage.getDataType()==MyDataInfo.MyMessage.DataType.StudentType){
            MyDataInfo.Student student = myMessage.getStudent();
            System.out.println(student);
        }else{
            MyDataInfo.Worker worker = myMessage.getWorker();
            System.out.println(worker);
        }

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端已连接:"+ctx.channel().remoteAddress());
    }
}