Netty -08- Google Protobuf

编码和解码的基本介绍

  • 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据 时就需要解码
  • codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。encoder 负责把业务数据转换成字节 码数据,decoder 负责把字节码数据转换成业务数据


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

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

  2. Netty 提供的编码器

    • StringEncoder,对字符串数据进行编码

    • ObjectEncoder,对 Java 对象进行编码

  3. Netty 提供的解码器

    • StringDecoder, 对字符串数据进行解码
    • ObjectDecoder,对 Java 对象进行解码
  4. Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象的编码和解码,尚硅谷 Netty 核心技术及源码剖析 底层使用的仍是 Java 序列化技术 , 而 Java 序列化技术本身效率就不高,存在如下问题

    • 无法跨语言
    • 序列化后的体积太大,是二进制编码的 5 倍多
    • 序列化性能太低
  5. 引出 新的解决方案 [Google 的 Protobuf]


Protobuf

  1. Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式
  2. 目前很多公司http+json 转为了 tcp+protobuf
  3. Protobuf 是以 message 的方式来管理数据的.
  4. 支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言,例如 C++、 C#、Java、python 等)
  5. 高性能,高可靠性
  6. 使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描述。说明,在 idea 中编 写 .proto 文件时,会自动提示是否下载 .ptotot 编写插件. 可以让语法高亮。
  7. 然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件
  8. protobuf 使用示意图

参考文档 : https://developers.google.com/protocol-buffers/docs/proto


单个pojo

  1. 客户端可以发送一个 Student PoJo 对象到服务器 (通过 Protobuf 编码)
  2. 服务端能接收 Student PoJo 对象,并显示信息(通过 Protobuf 解码)

server
public class Server {
    public static void main(String[] args) throws IOException {
        //创建BossGroup和WorkerGroup
        //bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
        EventLoopGroup bossGroup=new NioEventLoopGroup(5);
        EventLoopGroup workerGroup=new NioEventLoopGroup();

        //创建服务器端的启动对象,配置参数
        ServerBootstrap  bootstrap=new ServerBootstrap();

        try {
            //使用链式编程来进行设置
            bootstrap.group(bossGroup,workerGroup)  //设置两个线程
                .channel(NioServerSocketChannel.class)  //使用NioServerSocketChannel,作为服务器的通道实现
                .option(ChannelOption.SO_BACKLOG,128)  //设置线程队列里连接个数
                .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() {  //handler对应的是boosGruop,childHandler对应的是workerGroup
                    //给pipeline设置处理器
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();

                        //加入解码器
                        //指定对哪种对象进行解码
                        pipeline.addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));

                        pipeline.addLast(new ServerHandler());
                    }
                });  //给我们的workerGroup的EventLoop对应的管道设置处理器
            System.out.println("服务器 is ready ...");

            //绑定一个端口并且同步,生成一个ChannelFuture对象
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给 cf 注册监听器,监控我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if(future.isSuccess()){
                        System.out.println("监听端口成功");
                    }else{
                        System.out.println("监听端口失败");
                    }
                }
            });

            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

serverHandler
public class ServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {
        System.out.println("客户端发送的数据:"+msg.getId()+":"+msg.getName());
    }

    /**
    * @Description 数据读取完毕
    * @date 2020/7/23 10:46
    * @param ctx  
    * @return void
    */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入到缓冲并刷新
        //一般来讲,我们队发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
    }

    /**
    * @Description 处理异常,一般是需要管理通道
    * @date 2020/7/23 11:13
    * @param ctx
    * @param cause
    * @return void
    */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}


client
public class Client {
    public static void main(String[] args) {
        //客户端需要一个事件循环组
        EventLoopGroup eventExecutors=new NioEventLoopGroup();

        try {
            //创建客户端启动对象
            Bootstrap bootstrap=new Bootstrap();

            //设置相关参数
            bootstrap.group(eventExecutors) //设置线程组
                .channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        //加入编码器
                        pipeline.addLast("encoder",new ProtobufEncoder());
                        pipeline.addLast(new ClientHandler());     //加入自己的处理器
                    }
                });
            System.out.println("客户端 ok...");

            //启动客户端去连接服务器端
            //关于ChannelFuture要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 6668)).sync();

            //关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            eventExecutors.shutdownGracefully();
        }
    }
}

clientHandler
public class ClientHandler extends ChannelInboundHandlerAdapter {

    /**
    * @Description 当通道就绪时会触发该方法
    * @date 2020/7/23 11:27
    * @param ctx
    * @return void
    */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("重庆刘德华,香港徐大虾").build();

        ctx.writeAndFlush(student);
    }

    /**
    * @Description 当通道有读取事件时,会触发
    * @date 2020/7/23 11:29
    * @param ctx
    * @param msg
    * @return void
    */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf=(ByteBuf)msg;
        System.out.println("服务器回复的消息:"+((ByteBuf) msg).toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }
}


Student.proto
syntax= "proto3";  //版本
option java_outer_classname="StudentPOJO"; //生成的外部类名
message Student{
    int32 id=1;
    string name=2;
}

操作

protoc.exe --java_out=. Student.proto


运行


多个pojo

server
public class Server {
    public static void main(String[] args) throws IOException {
        //创建BossGroup和WorkerGroup
        //bossGroup和workerGroup含有的子线程的个数,默认为cpu核数*2
        EventLoopGroup bossGroup=new NioEventLoopGroup(5);
        EventLoopGroup workerGroup=new NioEventLoopGroup();

        //创建服务器端的启动对象,配置参数
        ServerBootstrap  bootstrap=new ServerBootstrap();

        try {
            //使用链式编程来进行设置
            bootstrap.group(bossGroup,workerGroup)  //设置两个线程
                .channel(NioServerSocketChannel.class)  //使用NioServerSocketChannel,作为服务器的通道实现
                .option(ChannelOption.SO_BACKLOG,128)  //设置线程队列里连接个数
                .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持连接状态
                .childHandler(new ChannelInitializer<SocketChannel>() {  //handler对应的是boosGruop,childHandler对应的是workerGroup
                    //给pipeline设置处理器
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();

                        //加入解码器
                        //指定对哪种对象进行解码
                        pipeline.addLast("decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));


                        pipeline.addLast(new ServerHandler());
                    }
                });  //给我们的workerGroup的EventLoop对应的管道设置处理器
            System.out.println("服务器 is ready ...");

            //绑定一个端口并且同步,生成一个ChannelFuture对象
            ChannelFuture cf = bootstrap.bind(6668).sync();

            //给 cf 注册监听器,监控我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if(future.isSuccess()){
                        System.out.println("监听端口成功");
                    }else{
                        System.out.println("监听端口失败");
                    }
                }
            });

            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

serverHandler
public class ServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
        //根据dataType,显示不同的信息
        MyDataInfo.MyMessage.DateType dateType=msg.getDateType();

        if(dateType==MyDataInfo.MyMessage.DateType.StudentType){
            MyDataInfo.Student student=msg.getStudent();
            System.out.println("学生的信息:"+student.getId()+":"+student.getName());
        }else if(dateType==MyDataInfo.MyMessage.DateType.WorkerType){
            MyDataInfo.Worker worker=msg.getWorker();
            System.out.println("工人的信息:"+worker.getName()+":"+worker.getAge());
        }else{
            System.out.println("传输的类型不正确");
        }
    }

    /**
    * @Description 数据读取完毕
    * @date 2020/7/23 10:46
    * @param ctx  
    * @return void
    */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入到缓冲并刷新
        //一般来讲,我们队发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer(new Date().toString()+":"+"hello,客户端",CharsetUtil.UTF_8));
    }

    /**
    * @Description 处理异常,一般是需要管理通道
    * @date 2020/7/23 11:13
    * @param ctx
    * @param cause
    * @return void
    */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

client
public class Client {
    public static void main(String[] args) {
        //客户端需要一个事件循环组
        EventLoopGroup eventExecutors=new NioEventLoopGroup();

        try {
            //创建客户端启动对象
            Bootstrap bootstrap=new Bootstrap();

            //设置相关参数
            bootstrap.group(eventExecutors) //设置线程组
                .channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        //加入编码器
                        pipeline.addLast("encoder",new ProtobufEncoder());
                        pipeline.addLast(new ClientHandler());     //加入自己的处理器
                    }
                });
            System.out.println("客户端 ok...");

            //启动客户端去连接服务器端
            //关于ChannelFuture要分析,涉及到netty的异步模型
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 6668)).sync();

            //关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            eventExecutors.shutdownGracefully();
        }
    }
}

clientHandler
public class ClientHandler extends ChannelInboundHandlerAdapter {

    /**
    * @Description 当通道就绪时会触发该方法
    * @date 2020/7/23 11:27
    * @param ctx
    * @return void
    */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        int random = new Random().nextInt(3);
        MyDataInfo.MyMessage myMessage = null;
        System.out.println(random);
        if(random==0){
            myMessage=MyDataInfo.MyMessage.newBuilder().setDateType(MyDataInfo.MyMessage.DateType.StudentType)
                .setStudent(MyDataInfo.Student.newBuilder().setName("重庆刘德华").setId(2).build()).build();
        }else{
            myMessage=MyDataInfo.MyMessage.newBuilder().setDateType(MyDataInfo.MyMessage.DateType.WorkerType)
                .setWorker(MyDataInfo.Worker.newBuilder().setName("香港徐大虾").setAge(21).build()).build();
        }


        ctx.writeAndFlush(myMessage);
    }

    /**
    * @Description 当通道有读取事件时,会触发
    * @date 2020/7/23 11:29
    * @param ctx
    * @param msg
    * @return void
    */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf=(ByteBuf)msg;
        System.out.println("服务器回复的消息:"+((ByteBuf) msg).toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }
}

Message.proto
syntax = "proto3";
option optimize_for= SPEED;  //快速解析
option java_package="top.codekiller.test.nettty.codec2"; //指定生成到哪个包下
option java_outer_classname="MyDataInfo";  //外部类名称

//protobuf 可以使用message管理其它的message
message MyMessage{
    //定义一个枚举
    enum DateType{
        StudentType=0;  //在proto3 要求enum的编号从0开始
        WorkerType=1;
    }

    //用data_type 来标识传的是哪一个枚举类型
    DateType date_type=1;

    //表示每次枚举类型最多只能出现其中的一个,节省空间
    oneof dataBody{
        Student student=2;
        Worker worker=3;
    }
}

message Student{
    int32 id=1;
    string name=2;
}

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

操作

 👉 查看操作


运行


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值