【Netty】模型篇二:通过案例分析Netty线程模型以及Netty的核心组件

1 回顾一下上一篇文章讲的Netty执行流程

详细可以参考:【Netty】模型篇一:Netty 线程模型架构 & 工作原理 解读

Netty 线程模型详细说明

  • Netty抽象出两组 线程池BossGroupWorkerGroup
    • BossGroup 专门负责接收客户端的连接
    • WorkerGroup 专门负责网络的读写
  • BossGroupWorkerGroup 类型都是 NioEventLoopGroup,NioEventLoopGroup 相当于一个 事件循环组,这个组中 含有多个事件循环 ,每一个事件循环是 NioEventLoop
  • NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯
  • NioEventLoopGroup 可以有多个线程, 即可以含有多个NioEventLoop
  • 每个Boss Group 中的 NioEventLoop 循环执行的步骤:
    • 1)轮询accept 事件
    • 2)处理accept 事件,与client建立连接 , 生成 NioScocketChannel并将其注册 Worker Group 上的某个 NIOEventLoop 上的 selector
    • 3)处理任务队列的任务,即 runAllTasks
  • 每个 Worker Group 中的 NIOEventLoop 循环执行的步骤:
    • 1)轮询 read/write 事件
    • 2)处理 I/O 事件, 即 read/write 事件,在对应的 NioScocketChannel 上处理
    • 3)处理任务队列的任务 , 即 runAllTasks
  • 每个Worker NIOEventLoop 处理业务时,会使用 pipeline(管道)。pipline中包含了 channel,即通过pipline可以获取到对应的 channel,并且pipline维护了很多的 handler(处理器)来对我们的数据进行一系列的处理。
  • handler(处理器) 有Netty内置的,我们也可以自己定义。
    在这里插入图片描述

2 Netty 快速入门案例

下面我们通过一个案例来进一步深入理解上一节讲解的Netty线程模型的工作原理,并对其中涉及到的Netty核心组件进行详细分析

案例要求

  • Netty 服务器在 6668 端口监听,浏览器发出请求 http://localhost:6668/
  • 服务器可以回复消息给客户端 "Hello! 我是服务器 5 ",并对特定请求资源进行过滤;
  • 目的:Netty 可以做Http服务开发,并且理解Handler实例和客户端及其请求的关系。

2.1 服务端代码

2.1.1 代码实现思路详解

步骤一:创建两个线程组 BossGroup 和 WorkerGroup,它们的类型都是 NioEventLoopGroup

  • BossGroup 只处理连接请求
  • WorkerGroup处理客户端业务
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

步骤二:创建服务器端启动对象 ServerBootstrap ,并进行参数配置:

  • 设置 BossGroup 和 WorkerGroup
  • 设置使用使用 NioSocketChannel 作为服务器的通道实现
  • 设置保持活动连接
  • 给 WorkerGroup 的 EventLoop 对应的 Pipline 设置 Handler,Handler可以是Netty内置的,也可以自己实现
// 2.创建服务器端启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
// 使用链式编程进行设置
bootstrap.group(bossGroup,workerGroup)// 设置两个线程组
        .channel(NioServerSocketChannel.class)// 使用 NioSocketChannel 作为服务器的通道实现
        .option(ChannelOption.SO_BACKLOG,128)// 设置线程队列得到连接个数
        .childOption(ChannelOption.SO_KEEPALIVE,true)// 设置保持活动连接
        // 给 WorkerGroup 的 EventLoop 对应的 管道 设置处理器
        .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
            // 向 pipline 设置 处理器
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                // 通过 channel 可以拿到 pipline,也可以通过 pipline 拿到 channel
                socketChannel.pipeline()
                        .addLast(new NettyServerHandler());// 向 pipline 添加 处理器
            }
        });

步骤三:实现Handler对象

  • 如果想自定义Handler,则需要继承Netty规定好的某个 HandlerAdapter
  • 这里先重写三个常用方法:
    • channelRead():读取客户端的消息
    • channelReadComplete():消息读取完毕,向客户端回复消息
    • exceptionCaught():处理异常,一般需要关闭通道
/*
说明:
1.自定义Handler,需要继承Netty规定好的某个HandlerAdapter
2.这时我们自定义的Handler,才能称为一个Handler
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    

    /*
    作用:读取客户端的消息
    ChannelHandlerContext ctx:上下文对象,含有 管道pipline、通道channel、地址等
    Object msg:就是客户端发送的数据 默认 Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        System.out.println("server ctx = "+ctx);
        // 将 msg 转成 ByteBuffer
        // ByteBuf 是 Netty 提供的,不是 NIO 中的
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("客户端发送的消息是:"+buffer.toString(CharsetUtil.UTF_8));
        System.out.println("客户端的地址是:"+ctx.channel().remoteAddress());
    }
    // 数据读取完毕,向客户端回复消息
    @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();
    }
}

步骤四:绑定端口并且同步,启动服务器,生成并返回一个 ChannelFuture 对象

ChannelFuture cf = bootstrap.bind(6668).sync();

步骤五:设置对关闭通道事件进行监听

// 对关闭通道事件进行监听
channelFuture.channel().closeFuture().sync();

2.1.2 最终代码

public class NettyServer {
    
    
    public static void main(String[] args)  {
    
    
        // 1.创建 BossGroup 和 WorkerGroup
        /*
        说明:
            1.创建两个线程组 BossGroup 和 WorkerGroup
            2.bossGroup 只处理连接请求,workerGroup 处理客户端业务
            3.两个都是无限循环
            4.BossGroup 和 WorkerGroup 还有的子线程(NioEventLoop)的个数
                默认是 cpu核数 * 2
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
    
    
            // 2.创建服务器端启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 使用链式编程进行设置
            bootstrap.group(bossGroup,workerGroup)// 设置两个线程组
                    .channel(NioServerSocketChannel.class)// 使用 NioSocketChannel 作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG,128)// 设置线程队列得到连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE,true)// 设置保持活动连接
                    // 给 WorkerGroup 的 EventLoop 对应的 管道 设置处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    // 创建一个通道测试对象(匿名对象)
                        // 向 pipline 设置 处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                            // 通过 channel 可以拿到 pipline,也可以通过 pipline 拿到 channel
                            socketChannel.pipeline()
                                    .addLast(new NettyServerHandler());// 向 pipline 添加 处理器
                        }
                    });
            System.out.println("服务器就绪...");
            // 3.绑定端口并且同步,生成一个 ChannelFuture 对象
            // 这里服务器就已经启动服务器了
            ChannelFuture cf = bootstrap.bind(6668).sync();

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

我们可以看到上面为 pipline 设置了一个handler对象,因此我们来实现一下 handler对象

/*
说明:
1.自定义Handler,需要继承Netty规定好的某个HandlerAdapter
2.这时我们自定义的Handler,才能称为一个Handler
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    

    /*
    作用:读取客户端的消息
    ChannelHandlerContext ctx:上下文对象,含有 管道pipline、通道channel、地址等
    Object msg:就是客户端发送的数据 默认 Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        System.out.println("server ctx = "+ctx);
        // 将 msg 转成 ByteBuffer
        // ByteBuf 是 Netty 提供的,不是 NIO 中的
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("客户端发送的消息是:"+buffer.toString(CharsetUtil.UTF_8));
        System.out.println("客户端的地址是:"+ctx.channel().remoteAddress());
    }
    // 数据读取完毕,向客户端回复消息
    @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();
    }
}

2.2 客户端代码

2.2.1 代码实现思路详解

步骤一:创建一个线程组 group ,它的类型是 NioEventLoopGroup。

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

步骤二:创建客户端启动对象 Bootstrap ,并进行参数配置:

  • 设置 group
  • 设置使用使用 NioSocketChannel 作为客户端通道的实现类
  • 给 WorkerGroup 的 EventLoop 对应的 Pipline 设置 Handler,Handler可以是Netty内置的,也可以自己实现
Bootstrap bootstrap = new Bootstrap();
// 设置相关参数,链式编程
bootstrap.group(group)// 设置线程组
        .channel(NioSocketChannel.class)// 设置客户端通道的实现类
        .handler(new ChannelInitializer<SocketChannel>() {
    
    
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
    
    
                ch.pipeline().addLast(new NettyClientHandler());// 加自己的处理器
            }
        });

步骤三:实现Handler对象

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
    

    // 当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("client ctx = "+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,服务端...", CharsetUtil.UTF_8));
    }
    // 当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+buffer.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }
    // 发生异常时处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
        ctx.close();
    }
}

步骤四:绑定 IP 和 端口 并且同步,启动客户端,生成并返回一个 ChannelFuture 对象

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

步骤五:设置对关闭通道事件进行监听

// 对关闭通道事件进行监听
channelFuture.channel().closeFuture().sync();

2.2.2 最终代码

public class NettyClient {
    
    
    public static void main(String[] args) {
    
    
        // 客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
    
    
            // 创建客户端启动对象
            // 注意:客户端使用的是 Bootstrap 而不是 ServerBootStrap
            Bootstrap bootstrap = new Bootstrap();
            // 设置相关参数,链式编程
            bootstrap.group(group)// 设置线程组
                    .channel(NioSocketChannel.class)// 设置客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
    
    
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            ch.pipeline().addLast(new NettyClientHandler());// 加自己的处理器
                        }
                    });
            System.out.println("客户端就绪...");
            // 启动客户端连接服务端
            // 关于 ChannelFuture 涉及到 netty 的异步模式
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();

            // 对关闭通道事件进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            group.shutdownGracefully();
        }
    } 
}

这里也需要为 pipline 设置了一个handler对象,因此我们来实现一下 handler对象

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
    

    // 当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("client ctx = "+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,服务端...", CharsetUtil.UTF_8));
    }
    // 当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+buffer.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }
    // 发生异常时处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
        ctx.close();
    }
}

3 代码分析

3.1 代码总结

在这里插入图片描述

由于客户端和服务端代码类似,所以我们重点分析服务端的代码。

在服务端创建了两个 线程池BossGroupWorkerGroup ,BossGroup 专门负责接收客户端的连接,WorkerGroup 专门负责网络的读写。

  • BossGroupWorkerGroup 类型都是 NioEventLoopGroup,NioEventLoopGroup 相当于一个 事件循环组,这个组中 含有多个事件循环 NioEventLoop ,每一个事件循环是 NioEventLoop
  • NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯

每个Boss Group 中的 NioEventLoop 循环执行的步骤:

  • 1)轮询accept 事件
  • 2)处理accept 事件,与client建立连接 , 生成 NioScocketChannel并将其注册 Worker Group 上的某个 NIOEventLoop 上的 selector
  • 3)处理任务队列的任务,即 runAllTasks

每个 Worker Group 中的 NIOEventLoop 循环执行的步骤:

  • 1)轮询 read/write 事件
  • 2)处理 I/O 事件, 即 read/write 事件,在对应的 NioScocketChannel 上处理
  • 3)处理任务队列的任务 , 即 runAllTasks

每个Worker NIOEventLoop 处理业务时,会使用 pipeline(管道)。pipline中包含了 channel,即通过pipline可以获取到对应的 channel,并且pipline维护了很多的 handler(处理器)来对我们的数据进行一系列的处理。handler(处理器) 可以是Netty内置的,我们也可以自己定义。

3.2 涉及到的Netty组件总结

Netty核心组件详解我们另写一篇文章分析,这里只简要说明用到了哪些组件。

  • 在创建线程组的时候用到了 NioEventLoopGroup类
  • 在创建服务端启动对象时用到了 ServerBootstrap 类;在创建客户端启动对象时用到了 Bootstrap 类
  • 在为启动对象配置参数时我们用到了 NioServerSocketChannel类ChannelOption类ChannelInitializer类;ChannelInitializer类继承了 ChannelInboundHandlerAdapter类
  • 在自定义Handler时,我们继承了 ChannelInboundHandlerAdapter 类;在重写该类的方法中涉及到了 ChannelHandlerContext 类ByteBuf 类Unpooled类
  • 在绑定端口并且同步,启动服务器,生成并返回一个 ChannelFuture类 的对象。

对于这些组件的用法,大家可以参考下一篇文章:【Netty】模型篇三:Netty核心组件讲解

猜你喜欢

转载自blog.csdn.net/qq_36389060/article/details/124427870