Netty编码解码介绍以及Protobuf的简单使用

编码与解码:

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

netty自带的编解码器

netty提供的编码器:
stringEncoder:对字符串数据进行编码
objectEncoder:对java对象进行编码
netty提供的解码器:
StringDecoder :对字符串进行解码
ObjectDecoder :对java对象进行解码

netty自带的编解码器的问题以及解决方案

因为Netty自带的编解码器底层使用的是java 序列化技术,而java的序列化技术本身效率就不高,存在以下问题:
1.无法跨语言
2.序列化后的体积太大,是二进制编码的5倍多
3.序列化性能太低
解决方案:
Google–Protobuf

Protobuf

1.Protobuf 是Google发布的开源项目,全程Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化,它很适合做数据存储活RPC数据交换格式。
2. 参考文档:https://developers.google.com/protocol-buffers/docs/proto
3. Protobuf是以message的方式来管理数据的。
4. 支持跨平台,跨语言,即客户端和服务端可以是不同的语言编写的。
5. 高性能,高可靠性。
6. 使用 protobuf编译器能自动生成代码,Protobuf 是将类的定义使用.proto文件进行描述,说明,在idea中编写.proto文件时,会自动提示是否下载.ptotot编写的插件,可以让语法高亮。
7. 然后通过protoc.exe编译器根据.proto自动生成.java文件
8. Protobuf示意图:
在这里插入图片描述

Protobuf使用案例:

需求:我们通过Protobuf,从客户端发送一个Student对象到服务器端

0.我们使用的是Maven项目,导入Protobuf的依赖

注意我们这次使用的版本为3.6.1

		<dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.6.1</version>
        </dependency>
1.新建一个.proto文件

new — File 这里我们叫做 Student.proto
当我们第一次创建这个文件的时候,IDEA会提示我们安装一个插件,我们安装即可,确定安装成功后,需要重启我们的IDEA
然后发现创建的Student.proto图标变了 说明安装成功
在这里插入图片描述

2. 编写我们的Student.proto文件
syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO"; // 生成外部类名,同时也是文件名
// protoBuf 是以message管理数据
message Student { // 会在StudentPOJO 外部类生成一个内部类Student,他才是真正发送的对象
    int32 id = 1 ; // Student 类中有一个属性,名字为id int32 对应java中的int 1表示属性序号,不是值
    string name = 2;
}

syntax : 我们proto的版本
option java_outer_classname :生成的外部类名称,即自定生成的类名
message :是以message管理数据。
注意:名字为id int32 对应java中的int 1表示属性序号,不是值,而且这里的字符串对应string,第一个字母是小写

3. 通过protoc3.6.1.exe自动生成java文件

protoc3.6.1.exe下载地址:
https://download.csdn.net/download/weixin_43326401/12151500

将我们的Student.proto 放在与protoc3.6.1.exe的同一目录,并用linux生成java文件
在这里插入图片描述
然后cmd 运行,进入protoc3.6.1.exe所在的文件夹,输入命令 protoc3.6.1.exe --java_out=. Student.proto
在这里插入图片描述
如果窗口没报错,就会在同一目录下生成一个java文件:
在这里插入图片描述
然后我们将生成的java文件复制进我们的项目当中
在这里插入图片描述
如果该类中有报错:
我们需要 file—Project Structure,这里我使用的是jdk1.8,所以调成对应的版本
在这里插入图片描述
然后我们file ----setting
在这里插入图片描述
也需要改成对应的jdk版本,然后就不报错了

4.准备工作就OK了 编写服务端
package com.jym.codec;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;

/**
 * @program: NettyPro
 * @description: Netty服务端练习
 * @author: jym
 * @create: 2020/02/07
 */
public class JymNettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建bossGroup,workGroup
        // 1.创建两个线程组bossGroup和workGroup
        // 2.bossGroup只是处理连接请求,真正的和客户端处理,会交给workGroup完成
        // 3.两个都是无限循环
        // 4.bossGroup和workGroup还有的子线程(NioEventLoop)的个数,默认实际CPU的核数*2
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            // 创建服务器端启动的对象,可以配置启动参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 使用链式编程来进行设置
            // 设置两个线程组
            serverBootstrap.group(bossGroup,workGroup)
                    // 使用NioServerSocketChannel作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    // 设置线程队列得到连接个数
                    .option(ChannelOption.SO_BACKLOG,128)
                    // 设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    // 给我们的workGroup的EventLoop对应的管道设置处理器
                    // 创建一个通道测试对象
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 给pipeline设置处理区
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 解码 指定对哪种对象进行解码
                            pipeline.addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new JymNettyServerHandler());
                        }
                    });
            System.out.println("服务器 is ready");
            // 绑定端口并且同步,生成一个ChannelFuture对象,启动服务器
            ChannelFuture sync = serverBootstrap.bind(6668).sync();
            // 对关闭通道进行监听
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }


    }
}

这里特殊说明一下:
在这里插入图片描述
ProtobufDecoder:构造方法里传入的,是指定对哪种对象进行解码,因为我们是对Student进行解码,所以是StudentPOJO.Student.getDefaultInstance()

5.服务端自定义handler:
package com.jym.codec;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @program: NettyPro
 * @description: 1.自定义handler,需要继承netty规定好的某个HandlerAdapter(规范)
 * @description: 2.这时我们的自定义handler,才能称为一个handler
 * @author: jym
 * @create: 2020/02/08
 */
public class JymNettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     *  读取数据实际(这里我们可以读取从客户端发来的消息)
     *  1.ChannelHandlerContext ctx:上下文对象,含有管道pipeline,通道,地址
     *  2.Object msg: 客户端发送的数据,默认是object的形式
     */
    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        // 读取客户端发送的studentPojo.student
        StudentPOJO.Student student = (StudentPOJO.Student) msg;
        System.out.println("客户端发送的数据:"+student.getId() + student.getName());
    }

    /**
     * 数据读取完毕
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // writeAndFlush 是write+flush,将数据写入到缓存,并刷新
        // 一般讲,我们对发送的数据进行一个编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端",CharsetUtil.UTF_8));
    }

    /**
     * 处理异常,一般是关闭连接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

6.客户端代码:
package com.jym.codec;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
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;

/**
 * @program: NettyPro
 * @description: Netty客户端练习
 * @author: jym
 * @create: 2020/02/08
 */
public class JymNettyClient {
    public static void main(String[] args) throws InterruptedException {
        // 客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            // 创建客户端启动对象,注意:客户端使用的不是SeverBootstrap,而是Bootstrap
            Bootstrap bootstrap = new Bootstrap();

            // 设置相关参数
            // 设置线程组
            bootstrap.group(group)
                    // 设置通道的实现类(反射)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //在pipeline 加入ProtoBufEncoder
                            pipeline.addLast(new ProtobufEncoder());
                            // 加入自己的处理器
                            pipeline.addLast(new JymNettyClientHandler());
                        }
                    });
            System.out.println("客户端 is ok");

            // 启动客户端去连接服务器端
            // 关于 ChannelFuture 要分析,涉及到netty的异步模型
            ChannelFuture sync = bootstrap.connect("127.0.0.1", 6668).sync();
            // 给关闭通道进行监听
            sync.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }

    }
}

ProtoBufEncoder:proto的编码器

7.客户端handler,给客户端发送消息
package com.jym.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @program: NettyPro
 * @description: 处理器
 * @author: jym
 * @create: 2020/02/08
 */
public class JymNettyClientHandler extends ChannelInboundHandlerAdapter {
    /**
     * 通道就绪就会触发该方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送一个student对象
        StudentPOJO.Student.Builder student = StudentPOJO.Student.newBuilder().setId(4).setName("惊讶猫");
        ctx.writeAndFlush(student);
    }

    /**
     * 通道有读取时间时触发
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+ byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:" + ctx.channel().remoteAddress());
    }

    /**
     * 处理异常,关闭上下文
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

上个例子有很大的局限性,比如我们如果传的不是student类型,还需要编写其他的.proto文件,所以需要进行一个简单的处理

.proto文件,这里我们添加个worker类型,如果需要发送别的类型的数据,也可以在里添加

syntax = "proto3";
option optimize_for = SPEED; // 加快解析
option java_package = "com.jym.codec2";  //指定生成到哪个包下
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;
    string name = 2;
}

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

服务端handler也需要改进一下

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        // 读取客户端发送的studentPojo.student
        MyDataInfo.MyMessage.DataType dataType = ((MyDataInfo.MyMessage) msg).getDataType();
        if(dataType == MyDataInfo.MyMessage.DataType.StudentType){
            MyDataInfo.Student student = ((MyDataInfo.MyMessage) msg).getStudent();
            System.out.println("学生id = "+ student.getId()+",学生name = "+student.getName());
        } else if(dataType == MyDataInfo.MyMessage.DataType.WorkerType){
            MyDataInfo.Worker worker = ((MyDataInfo.MyMessage) msg).getWorker();
            System.out.println("工人姓名= "+ worker.getName()+",工人年龄 = "+worker.getAge());
        } else {
            System.out.println("传输的类型不正确");
        }
    }

客户端handler

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 随机发送student 或者worker
        int i = new Random().nextInt(3);
        MyDataInfo.MyMessage myMessage = null;
        if(0 == i){
            myMessage = MyDataInfo.MyMessage.newBuilder()
                    .setDataType(MyDataInfo.MyMessage.DataType.StudentType)
                    .setStudent(MyDataInfo.Student.newBuilder().setId(9527).setName("惊讶猫")
                            .build()).build();
        } else {
            myMessage = MyDataInfo.MyMessage.newBuilder()
                    .setDataType(MyDataInfo.MyMessage.DataType.WorkerType)
                    .setWorker(MyDataInfo.Worker.newBuilder().setName("惊讶猫二号").setAge(22)
                            .build()).build();
        }

        ctx.writeAndFlush(myMessage);
    }

学习年限不足,知识过浅,说的不对请见谅。

世界上有10种人,一种是懂二进制的,一种是不懂二进制的。

发布了78 篇原创文章 · 获赞 54 · 访问量 46万+

猜你喜欢

转载自blog.csdn.net/weixin_43326401/article/details/104274035