Netty——二

七、Google Protobuf

1、编码和解码的基本介绍

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

image-20230228150935632

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

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

  2. Netty 提供的编码器

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

    • ObjectEncoder,对 Java 对象进行编码

    • …

  3. Netty 提供的解码器

    • StringDecoder, 对字符串数据进行解码

    • ObjectDecoder,对 Java 对象进行解码

    • …

  4. Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象 的编码和解码,底层使用的仍是 Java 序列化技术 , 而Java 序列化技术本身效率就不高,存 在如下问题:

    • 无法跨语言
    • 序列化后的体积太大,是二进制编码的 5 倍多。
    • 序列化性能太低

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

3、Protobuf

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

  2. 参考文档 : https://developers.google.com/protocol-buffers/docs/proto 语言指南 (翻墙)

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

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

  5. 高性能,高可靠性

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

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

  8. protobuf 使用示意图:

    image-20230228150645544

Protobuf快速入门实例

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

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

image-20230228151055659

引入依赖:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.6.1</version>
</dependency>

服务器端:

package com.lxg.netty.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;

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

        //创建BOSSGroup和WorkerGroup
        /**
         * 说明
         * 1、创建了两个线程组boosGroup和workerGroup
         * 2、boosGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
         * 3、两个都是无限循环
         * 4、bossGroup和workerGroup含有的子线程(NioEventLoop)的个数
         * 默认是以cpu核数*2
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

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


            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道来实现
                    .option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接的个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
                    //.handler(null) //该handler对应boosGroup,childHandler对应workGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个初始化对象(匿名对象)
                        //给pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline加入ProtobufDecoder
                            //指定哪种对象进行解码
                            pipeline.addLast("Decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });  //给我们的workerGroup的EventLoop对应的管道设置处理器

            System.out.println("服务器is ready......");

            //绑定一个端口并且同步,生成了一个ChannelFuture对象
            //启动服务器了(绑定好端口了)
            ChannelFuture cf = bootstrap.bind(6668).sync();

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

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

服务器端handler:

package com.lxg.netty.codec;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.CharsetUtil;

//继承入栈的handler适配器

/**
 * 1、自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
 * 2、这是我们自定义一个Handler,才能成为一个Handler
 */
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {

//    *
//     * 读取数据事件(这里我们可以读取客户端发送过来的消息)
//     *
//     * @param ctx :是上下文对象。含有管道pipeline,通道channel,地址
//     * @param msg :就是客户端发送过来的数据,默认是Object
//     * @throws Exception
//
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
//        //读取从客户端发送的StudentPOJO.Student
//        StudentPOJO.Student student = (StudentPOJO.Student) msg;
//        System.out.println("客户端发送的数据:id="+student.getId()+",名字="+student.getName());
//    }

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

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

    }

    /**
     * 处理异常,一般是需要关闭通道
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }


}

客户端:

package com.lxg.netty.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;

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

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();

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

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //在pipeline中加入ProtoBufEncoder
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast("encoder",new ProtobufEncoder());
                            pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });
            System.out.println("客户端 is ok !");

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

客户端handler:

package com.lxg.netty.codec;

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 {

    /**
     * 当通道就绪就会触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //发送一个Student对象到服务器
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("李四").build();
        //发送Teacher。Member......
        ctx.writeAndFlush(student);
    }


    /**
     * 当通道有读取事件时,会触发
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+buf.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:

syntax = "proto3";//版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf是使用message管理数据
message Student{//会在StudentPOJO外部类生成一个内部类Student,他是真正发送的POJO对象
  int32 id=1;//Student类中有一个属性 名字为id类型为int32(protobuf类型),1表示属性序号,不是值
  string name = 2;

}

转化为StudentPOJO.java

image-20230301145715697

Protobuf快速入门实例2

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

  1. 客户端可以随机发送Student PoJo/ Worker PoJo 对 象到服务器 (通过 Protobuf 编码)
  2. 服务端能接收Student PoJo/ Worker PoJo 对象(需要 判断是哪种类型),并显示信息(通过 Protobuf 解码)
  3. 具体 看演示步骤

服务器端:

package com.lxg.netty.codec2;

import com.lxg.netty.codec.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 {

        //创建BOSSGroup和WorkerGroup
        /**
         * 说明
         * 1、创建了两个线程组boosGroup和workerGroup
         * 2、boosGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
         * 3、两个都是无限循环
         * 4、bossGroup和workerGroup含有的子线程(NioEventLoop)的个数
         * 默认是以cpu核数*2
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

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


            //使用链式编程来进行设置
            bootstrap.group(bossGroup, workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道来实现
                    .option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接的个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
                    //.handler(null) //该handler对应boosGroup,childHandler对应workGroup
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个初始化对象(匿名对象)
                        //给pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline pipeline = ch.pipeline();
                            //在pipeline加入ProtobufDecoder
                            //指定哪种对象进行解码
                            pipeline.addLast("Decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });  //给我们的workerGroup的EventLoop对应的管道设置处理器

            System.out.println("服务器is ready......");

            //绑定一个端口并且同步,生成了一个ChannelFuture对象
            //启动服务器了(绑定好端口了)
            ChannelFuture cf = bootstrap.bind(6668).sync();

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

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

服务器端handler:

package com.lxg.netty.codec2;

import com.lxg.netty.codec.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

//继承入栈的handler适配器

/**
 * 1、自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
 * 2、这是我们自定义一个Handler,才能成为一个Handler
 */
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {

//    *
//     * 读取数据事件(这里我们可以读取客户端发送过来的消息)
//     *
//     * @param ctx :是上下文对象。含有管道pipeline,通道channel,地址
//     * @param msg :就是客户端发送过来的数据,默认是Object
//     * @throws Exception
//
//    @Override
//    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
//        //读取从客户端发送的StudentPOJO.Student
//        StudentPOJO.Student student = (StudentPOJO.Student) msg;
//        System.out.println("客户端发送的数据:id="+student.getId()+",名字="+student.getName());
//    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
        //根据datatype来显示不同的信息
        MyDataInfo.MyMessage.DataType dataType = msg.getDataType();
        if(dataType== MyDataInfo.MyMessage.DataType.StudentType){

            MyDataInfo.Student student = msg.getStudent();
            System.out.println("学生id为:"+student.getId()+",姓名为:"+student.getName());

        }else if (dataType== MyDataInfo.MyMessage.DataType.workerType){
            MyDataInfo.Worker worker = msg.getWorker();
            System.out.println("工人年龄为:"+worker.getAge()+",姓名为:"+worker.getName());

        }else{
            System.out.println("传输的数据类型不正确,请检查!");
        }
    }

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

    }

    /**
     * 处理异常,一般是需要关闭通道
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }


}

客户端:

package com.lxg.netty.codec2;

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;

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

        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();

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

            //设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //在pipeline中加入ProtoBufEncoder
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast("encoder",new ProtobufEncoder());
                            pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });
            System.out.println("客户端 is ok !");

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

客户端handler:

package com.lxg.netty.codec2;

import com.lxg.netty.codec.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

import java.util.Random;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当通道就绪就会触发该方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //随机发送一个Student或Worker对象到服务器
        int random = new Random().nextInt(3);
        MyDataInfo.MyMessage myMessage = null;
        if(0==random){//发送Student对象
            myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder()
                    .setId(5).setName("王五").build()).build();
        }else {//发送一个Worker对象
            myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.workerType).setWorker(MyDataInfo.Worker.newBuilder()
                    .setAge(20).setName("小林").build()).build();

        }
        ctx.writeAndFlush(myMessage);
    }


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

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


proto文件:

syntax = "proto3";
option optimize_for = SPEED;//加快解析
option java_package="com.lxg.netty.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;//Student类的属性
  string name=2;
}

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

八、Netty编解码器和handler的调用机制

1、基本说明

  1. netty的组件设计:Netty的主要组件有Channel、EventLoop、ChannelFuture、 ChannelHandler、ChannelPipe等

  2. ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实 现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),你就 可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响 应时,也可以从ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或 者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不 过它是用来处理出站数据的

  3. ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如 果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即 客户端发送给服务端的数据会通过pipeline中的一系列 ChannelOutboundHandler,并被这些Handler处理,反之则称为入站的

    image-20230228151325335

2、编码解码器

  1. 当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会 被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会 被编码成字节。
  2. Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者 ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。 以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后, 它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发 给ChannelPipeline中的下一个ChannelInboundHandler。

3、解码器-ByteToMessageDecoder

  1. 关系继承图

    image-20230228151713056

  2. 由于不可能知道远程节点是否会 一次性发送一个完整的信息, tcp有可能出现粘包拆包的问题, 这个类会对入站数据进行缓冲, 直到它准备好被处理

  3. 一个关于ByteToMessageDecoder实例分析

    public class ToIntegerDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
    {
    if (in.readableBytes() >= 4) {
    out.add(in.readInt());
    }
    }
    }
    说明:
    1) 这个例子,每次入站从ByteBuf中读取4字节,将其解码为一个int,然后将它添加到下一个List中。
    当没有更多元素可以被添加到该List中时,它的内容将会被发送给下一个ChannelInboundHandler。
    int在被添加到List中时,会被自动装箱为Integer。在调用readInt()方法前必须验证所输入的ByteBuf是
    否具有足够的数据
    2) decode 执行分析图 [示意图]
    

    image-20230228151738418

4、Netty的handler链的调用机制

实例要求:

  1. 使用自定义的编码器和解码器来 说明Netty的handler 调用机制

    客户端发送long -> 服务器 服务端发送long -> 客户端

  2. 案例演示

    image-20230228151757830

    image-20230301195441077

    服务端:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    public class MyServer {
        public static void main(String[] args) throws Exception {
            NioEventLoopGroup boosGroup = new NioEventLoopGroup(1);
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try{
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(boosGroup,workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new MyServerInitializer());//自定义一个初始化类
    
                ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
                channelFuture.channel().closeFuture().sync();
            }finally {
                boosGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    
    

    服务端Initializer:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
            //入站的handler进行解码MyByteToLongDecoder
            pipeline.addLast(new MyByteToLongDecoder());
            //出站的handler进行编码
            pipeline.addLast(new MyLongToByteEncoder());
            //加入自定义handler
            pipeline.addLast(new MyServerHandler());
        }
    }
    
    

    服务端handler:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    public class MyServerHandler extends SimpleChannelInboundHandler<Long> {
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
            System.out.println("从客户端:"+ctx.channel().remoteAddress()+"读取到long"+msg);
    
            //给客户端发送一个long
            ctx.writeAndFlush(98765L);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
    }
    
    

    Decoder:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ByteToMessageDecoder;
    import io.netty.util.concurrent.EventExecutorGroup;
    
    import java.util.List;
    
    public class MyByteToLongDecoder extends ByteToMessageDecoder {
    
        /**
         * decode会根据接收到的数据,被调用多次,直到确定没有新的元素被添加到list
         * 或者是ByteBuf没有更多的可读字节为止
         * 如果list out不为空,就会将list的内容传递给下一个channelinboundhandler处理
         * 该处理器的方法也会被调用多次
         *
         * @param ctx:上下文对象
         * @param byteBuf:入站的ByteBuf
         * @param list:list集合,将解码后的数据传给下一个handler
         * @throws Exception
         */
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
    
            System.out.println("MyByteToLongDecoder中的decode被调用了!");
            //因为long8个字节,需要判断有8个字节才能读取一个long
            if(byteBuf.readableBytes()>=8){
                list.add(byteBuf.readLong());
            }
        }
    }
    
    

    Encoder:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.buffer.ByteBuf;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    
    
    public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
        //编码方法
        @Override
        protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf byteBuf) throws Exception {
            System.out.println("MyLongToByteEncoder中encode被调用了!");
            System.out.println("msg="+msg);
            byteBuf.writeLong(msg);
        }
    }
    
    

    客户端:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    public class MyClient {
    
        public static void main(String[] args) throws Exception {
            NioEventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new MyClientInitializer());//自定义初始化类
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7000).sync();
                channelFuture.channel().closeFuture().sync();
    
            }finally {
                group.shutdownGracefully();
            }
        }
    }
    
    

    客户端Initializer:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
            //加入出站的handler对数据进行一个编码
            pipeline.addLast(new MyLongToByteEncoder());
    
            //这是入站的解码器(入站handler)
            pipeline.addLast(new MyByteToLongDecoder());
            //加入自定义的handler,处理业务
            pipeline.addLast(new MyClientHandler());
        }
    }
    
    

    客户端handler:

    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    import io.netty.util.concurrent.EventExecutorGroup;
    
    public class MyClientHandler extends SimpleChannelInboundHandler<Long> {
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, Long msg) throws Exception {
            System.out.println("服务器的ip="+channelHandlerContext.channel().remoteAddress());
            System.out.println("收到服务器的消息是="+msg);
        }
    
        //重写channelActive发送数据
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("MyClientHandler发送数据");
    //        ctx.writeAndFlush(Unpooled.copiedBuffer(""));
            ctx.writeAndFlush(123456L);//发送的是一个long
            //分析
            //1、"abcdabcdabcdacbd"是16个字节
            //2、该处理器的前一个handler是MyLongToByteEncoder
            //3、MyLongToByteEncoder父类MessageToByteEncoder会判断当前msg是不是应该处理的类型
            //如果是就处理,不是就跳过
            //4、因此我们在编写Encoder时要注意传入的数据类型和处理的数据类型一致
    //        ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdasdfasdf", CharsetUtil.UTF_8));
        }
    }
    
    
  1. 结论:

    • 不论解码器handler 还是 编码器handler 即接 收的消息类型必须与待处理的消息类型一致, 否则该handler不会被执行
    • 在解码器 进行数据解码时,需要判断 缓存 区(ByteBuf)的数据是否足够 ,否则接收到的结果和期望结果可能不一致

5、解码器-ReplayingDecoder

  1. public abstract class ReplayingDecoder extends ByteToMessageDecoder

  2. ReplayingDecoder扩展了ByteToMessageDecoder类,使用这个类,我们不必调用 readableBytes()方法。参数S指定了用户状态管理的类型,其中Void代表不需要状态 管理

  3. 应用实例:使用ReplayingDecoder 编写解码器,对前面的案例进行简化 [案例演示]

  4. ReplayingDecoder使用方便,但它也有一些局限性:

    • 并不是所有的 ByteBuf 操作都被支持,如果调用了一个不被支持的方法,将会抛出一个 UnsupportedOperationException。
    • ReplayingDecoder 在某些情况下可能稍慢于 ByteToMessageDecoder,例如网络缓慢并且消息格 式复杂时,消息会被拆成了多个碎片,速度变慢
    package com.lxg.netty.inboundhandlerandoutboundhandler;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ReplayingDecoder;
    
    import java.util.List;
    
    public class MyByteToLongDecoder2 extends ReplayingDecoder<Void> {
    
        @Override
        protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
            System.out.println("MyByteToLongDecoder2中的decode被调用了!");
            //在ReplayingDecoder中不需要判断数据是否足够读取,内部会进行处理判断
            list.add(byteBuf.readLong());
    
        }
    }
    
    

6、其它编解码器

其它解码器

  1. LineBasedFrameDecoder:这个类在Netty内 部也有使用,它使用行尾控制字符(\n或者\r\n) 作为分隔符来解析数据。
  2. DelimiterBasedFrameDecoder:使用自定义 的特殊字符作为消息的分隔符。
  3. HttpObjectDecoder:一个HTTP数据的解码器
  4. LengthFieldBasedFrameDecoder:通过指定 长度来标识整包消息,这样就可以自动的处理 黏包和半包消息。

image-20230228152033499

7、Log4j 整合到Netty

  1. 在Maven 中添加对Log4j的依赖 在 pom.xml

    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
    <scope>test</scope>
    </dependency>
    
  2. 配置 Log4j , 在 resources/log4j.propertie

    log4j.rootLogger=DEBUG,stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[%p] %C{1} - %m%n
    
  3. 演示整合

    image-20230228152445966

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值