编码和解码的基本介绍

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

image.png

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

  1. Netty自身提供了一些codec(编解码器)
  2. Netty提供的编码器
    1. StringEncoder, 对字符串数据进行编码
    2. ObjectEncoder, 对Java对象进行编码
    3. ……
  3. Netty提供的解码器
    1. StringDecoder: 对字符串数据进行解码
    2. ObjectDecoder: 对Java对象进行解码
    3. ……
  4. Netty本身自带的ObjectDecoder和ObjectEncoder可以用来实现Pojo对象或各种业务对象的编码和解码,底层使用的依然是Java序列化技术, 而Java序列化技术本身效率就不高, 存在如下问题
    1. 无法跨语言
    2. 序列化后的体积太大, 是二进制编码的5倍多
    3. 序列化性能太低
  5. => 引出新的解决方案[Google 的 ProtoBuf]

    Protobuf

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

  7. Protobuf是Google发布的开源项目, 全称 Google Protocol Buffers ,是一种 轻便高效的结构化数据存储格式,可以用于结构化数据串行化, 或者说序列化, 它很适合做数据存储或者RPC[远程过程调用]数据交换格式
    1. 目前很多公司 http + JSON -> tcp + protobuf
  8. 参考文档: https://developers.google.com/protocol-buffers/docs/proto 语言指南
  9. Protobuf是以message的方式来管理数据的
  10. 支持跨平台, 跨语言, 即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言, 例如C++, C#, Java, Python等)
  11. 高性能, 高可靠性
  12. 使用Protobuf编译器能自动生成代码, Protobuf是将类的定义使用.proto文件进行描述, 说明, 在IDEA中编写.proto文件时, 会自动提示是否下载 .proto编写插件, 可以让语法高亮
  13. 然后通过protoc.exe编译器根据.proto自动生成.java文件
  14. Protobuf使用示意图

image.png

Protobuf快速入门案例

编写程序, 使用Protobuf完成如下功能

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

    引入Protobuf的依赖

    1. <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
    2. <dependency>
    3. <groupId>com.google.protobuf</groupId>
    4. <artifactId>protobuf-java</artifactId>
    5. <version>3.6.1</version>
    6. </dependency>

    数据类型

    image.png

特殊字段

image.png
定义其他复杂类型参考:https://blog.csdn.net/lijingjingchn/article/details/89466437

软件

image.png

新建Student.proto

syntax = "proto3"; // 版本
option java_outer_classname = "StudentPOJO"; // Java的外部类名
// protobuf 使用message 管理数据
message Student { // 会在 StudentPOJO外部类中生成一个内部类 Student, 它是真正发送的POJO对象

  /**
    表示 在Student类中有一个属性名字为 id , 类型为int32(Protobuf类型) 1表示序号, 不是值
   */
  int32 id = 1;

  string name = 2;
}

根据.proto文件生成Java文件

将写好的文件放入bin文件夹中
image.png
在当前位置启动cmd
执行编译

protoc.exe --java_out=. Student.proto

image.png
将生成的文件拷贝到项目中
生成后的文件
不粘贴了[太大了],自己看一下吧

新建NettyServer

package com.dance.netty.netty.protobuf;

import com.dance.netty.netty.protobuf.pojo.StudentPOJO;
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;

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup) //设置两个线程组
                    .channel(NioServerSocketChannel.class) // 使用NioServerSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列等待连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 加入protobuf的解码器 指定解码默认实例
                            pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });

            System.out.println("server is ready......");
            ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

新建NettyServerhandler

package com.dance.netty.netty.protobuf;

import com.dance.netty.netty.protobuf.pojo.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(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 {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端", StandardCharsets.UTF_8));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

新建NettyClient

package com.dance.netty.netty.protobuf;

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.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;

public class NettyClient {

    public static void main(String[] args) throws InterruptedException {

        EventLoopGroup eventExecutors = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();

            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 加入处理Protobuf的编码器
                            pipeline.addLast(new ProtobufEncoder());
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("客户端 ok !");
            ChannelFuture sync = bootstrap.connect("127.0.0.1", 6668).sync();
            sync.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventExecutors.shutdownGracefully();
        }
    }

}

新建NettyClientHandler

package com.dance.netty.netty.protobuf;

import com.dance.netty.netty.protobuf.pojo.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.StandardCharsets;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 发送一个Student对象到服务器
        StudentPOJO.Student dance = StudentPOJO.Student.newBuilder().setId(4).setName("dance").build();
        ctx.writeAndFlush(dance);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务器回复的消息: " + byteBuf.toString(StandardCharsets.UTF_8));
        System.out.println("服务器地址: " + ctx.channel().remoteAddress());
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

测试

启动Server
启动Client

server is ready......
客户端发送的数据:4 dance
客户端 ok !
服务器回复的消息: Hello 客户端
服务器地址: /127.0.0.1:6668

使用SimpleChannelInBoundHandler定义泛型

package com.dance.netty.netty.protobuf;

import com.dance.netty.netty.protobuf.pojo.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.StandardCharsets;
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello 客户端", StandardCharsets.UTF_8));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }

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

Protobuf快速入门实例2

需求

  1. 编写程序, 使用Protobuf完成如下功能
  2. 客户端可以随机发送studentPOJO/workerPOJO对象到服务器(通过Protobuf编码)
  3. 服务器接收StudentPOJO/WorkerPOJO对象, (需要判断是那种类型), 并显示信息(通过Protobuf解码)
  4. 具体 看老师演示(show time)

    编写proto文件

    ```protobuf syntax = “proto3”; // 版本

option optimize_for = SPEED; // 加快解析

option java_package = “com.dance.netty.netty.protobuf2.pojo”; // 指定包

option java_outer_classname = “MyDataInfo”; // Java的外部类名

message MyMessage{ // 定义一个枚举类型 enum DataType { StudentType = 0; WorkerType = 1; }

// 用DataType来标识传的是哪个枚举类型 DataType dataType = 1;

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

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

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

<a name="it3KZ"></a>
## 生成java类型并拷贝到项目
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1603133/1642355225899-1e1fb139-c962-4a93-a454-aebfcdce45b2.png#clientId=u66ea8a04-e6a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u32c07338&margin=%5Bobject%20Object%5D&name=image.png&originHeight=119&originWidth=675&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10373&status=done&style=none&taskId=u6390539d-8413-4a64-8a5e-bf8dd300d23&title=)<br />好大~
<a name="wDQme"></a>
## 根据之前的案例1修改
<a name="K63PO"></a>
### 修改NettyClientHandler
```java
@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(1).setName("flower").build()).build();
  }else{
    myMessage = MyDataInfo.MyMessage.newBuilder()
      .setDataType(MyDataInfo.MyMessage.DataType.StudentType)
      .setWorker(MyDataInfo.Worker.newBuilder().setId(2).setName("dance").build()).build();
  }
  ctx.writeAndFlush(myMessage);

}

修改NettyServer

pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));

修改NettyServerHandler

public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
    if(msg.getDataType() == MyDataInfo.MyMessage.DataType.StudentType){
        System.out.println("客户端发送的数据:" + msg.getStudent().getId() + " " + msg.getStudent().getName());
    }else if(msg.getDataType() == MyDataInfo.MyMessage.DataType.WorkerType){
        System.out.println("客户端发送的数据:" + msg.getWorker().getId() + " " + msg.getWorker().getName());
    }
}

测试

client

客户端 ok !
服务器回复的消息: Hello 客户端
服务器地址: /127.0.0.1:6668

Server

server is ready......
客户端发送的数据:2 dance
客户端发送的数据:2 dance
客户端发送的数据:1 flower