Netty大战之Netty模型及核心组件

本文详细介绍了Netty的工作原理,从简单版到进阶版的模型图解,涵盖了BossGroup和WorkerGroup、NioEventLoop、Channel、Pipeline等核心组件。Netty基于主从Reactor多线程模型进行改进,其中EventLoopGroup用于处理IO事件,每个EventLoop包含一个Selector。文章还讲解了Handler的执行流程、任务队列的使用场景以及异步模型,展示了Netty在TCP服务、HTTP服务和群聊系统等应用场景中的实现细节。
摘要由CSDN通过智能技术生成

 工作原理示意图1-简单版

Netty 主要基于主从 Reactors 多线程模型(如图)做了一定的改进,其中主从 Reactor 多 线程模型有多个 Reactor

 说明

 工作原理示意图2-进阶版

1、Boos Groop和Worker Group内部都包含多个NioEventLoop(又叫循环事件)。

2、每个NIOEventLoop对应一个Selector,

工作原理示意图-详细版

 说明:

1、netty抽象出两个线程池BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络的读写。

2、BossGroup和WorkerGroup类型都是NioEventLoopGroup(事件循环组)。

3、NioEventLoopGroup相当于一个事件循环组,这个组中有多个事件循环,每一事件循环是NioEventLoop.

4、NioEventLoop表示的是一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定其上的socket的网络通讯。

5、NioEventLoopGroup可以有多个线程,并且可以设定个数,多少个线程对应的就是一个NioEventLoop

6、每个BossNioEventLoop循环的步骤有3步

1)轮训accept事件

2)处理accept事件,与client建立连接,生成SocketChannel在分装成NioSocketChannel,将其注册到某一个worker的NioEventLoop上的Selector上

3)处理任务队列的任务,及runAllTasks

7、每个worker NioEventLoop循环执行的步骤

1、轮训read,write事件

2、处理i/o事件,既read,write事件,在对应的NIoSockertChannel处理

3、处理任务队列的任务,既runAllTasks

8、每个worker NioEventLoop,处理业务时,会使用Pipeline(管道),pipeline中包含了Channel,即通过pipeline可以获取到对应的通道,管道中维护了很多的处理器。

友情提示:从源码中可以知道从Channel中可以获取Pipeline或者从Pipeline中获取到Channel,Channel主要作用是进行读写,Pipeline主要的作用是进行数据的业务处理,pipeline本质上是一个双向链表,包含一个出栈,入栈的问题

上下文对象包含

 channel

 pipeline

Netty 核心模块组

友情提示:感觉先介绍核心组件,在学习代码比较容易理解,Netty的代码都是这些组件结合实现代码的编写。

1、EventLoopGroup 和其实现类 NioEventLoopGroup

1.1、EventLoop

2、Bootstrap、ServerBootstrap

3、Channel

4、ChannelOption

5、Selector

6、ChannelHandler 及其实现类

7、ChannelHandlerContext

8、Pipeline 和 ChannelPipeline

9、Future、ChannelFuture、Promise

10、Unpooled

一开始需要树立正确的观念

  • 把 channel 理解为数据的通道,可以想象成走水路呢,空陆呢,还是路陆呢
  • 把 msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 的加工,会变成其它类型对象,最后输出又变成 ByteBuf,可以想象成交通工具,飞机,轮船,火车
  • 把 handler 理解为数据的处理工序,可以想象成,到达目的的每一小步,到每一个站点使用什么方式去一个站点,比如我想去北京,我做火车到南京,到了南京做轮船,去上海,到了上海我做飞机去北京。这每一个站点就是一个Handler.
    • 工序有多道,合在一起就是 pipeline,可以想象成,这完整的到达目的的线程流程就是Pipeline。pipeline 负责发布事件(读、读取完成...)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)
    • handler 分 Inbound 和 Outbound 两类,可以想象成去北京,然后从北京回来。
  • 把 eventLoop 理解为处理数据的工人(真正干活的,可以想象成,就是自己
    • 工人可以管理多个 channel 的 io 操作,并且一旦工人负责了某个 channel,就要负责到底(绑定)
    • 工人既可以执行 io 操作,也可以进行任务处理,每位工人有任务队列,队列里可以堆放多个 channel 的待处理任务,任务分为普通任务、定时任务
    • 工人按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每道工序指定不同的工人

EventLoopGroup 和其实现类 NioEventLoopGroup

友情提示:

NioEventLoopGroup可以执行 io事件,普通任务,定时任务

DefaultEventLoopGroup只能执行普通任务,定时任务

友情提示:

    EventLoopGroup 内部包含多个EventLoop,每个EventLoop关联一个Selector,多个Channel注册到Selector,BoosGroup的Selector主要监听Channel的连接,WorkerGroup的Selector主要监听Channel的读写操作。当BoosGroup的Selector监听到Channel的连接后,就会将其SockertChannel分装成功NioSocketChannel,WorkerGroup会调用next接口,调用其中的一个EventLoop将其注册到该EventLoop的Selector进行监听。

事件循环组

EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)

  • 继承自 netty 自己的 EventExecutorGroup
    • 实现了 Iterable 接口提供遍历 EventLoop 的能力
    • 另有 next 方法获取集合中下一个 EventLoop

 常用方法

 代码:

package cn.itcast.netty.c3;

import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.NettyRuntime;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j
public class TestEventLoop {
    public static void main(String[] args) {
        // 1. 创建事件循环组
        EventLoopGroup group = new NioEventLoopGroup(2); // io事件,普通任务,定时任务
//        EventLoopGroup group = new DefaultEventLoopGroup(); // 普通任务,定时任务
        // 2. 获取下一个事件循环对象
        System.out.println(group.next());
        System.out.println(group.next());
        System.out.println(group.next());
        System.out.println(group.next());

        // 3. 执行普通任务
//  execute也可以换成submit,group.next().submit

        /*group.next().execute(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("ok");
        });*/

        // 4. 执行定时任务,以一定的频率执行
        group.next().scheduleAtFixedRate(() -> {
            log.debug("ok");
//            第一个参数初始的延迟时间,如果为0就是立刻执行
//            第二个参数,就是每隔多长时间执行。
        }, 0, 1, TimeUnit.SECONDS);

        log.debug("main");
    }
}

演示 NioEventLoop 处理 io 事件

服务器端两个 nio worker 工人

package cn.itcast.netty.c3;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class EventLoopServer {
    public static void main(String[] args) {
        // 细分2:创建一个独立的 EventLoopGroup
        EventLoopGroup group = new DefaultEventLoopGroup();
        new ServerBootstrap()
                // boss 和 worker
                // 细分1:boss 只负责 ServerSocketChannel 上 accept 事件     worker 只负责 socketChannel 上的读写
                .group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug(buf.toString(Charset.defaultCharset()));
                                ctx.fireChannelRead(msg); // 让消息传递给下一个handler
                            }
                        });
                        /*.addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug(buf.toString(Charset.defaultCharset()));
                            }
                        });*/
                    }
                })
                .bind(8080);
    }
}

结论:

可以看到两个工人轮流处理 channel,但工人与 channel 之间进行了绑定

再增加两个非 nio 工人

package cn.itcast.netty.c3;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class EventLoopServer {
    public static void main(String[] args) {
        // 细分2:创建一个独立的 EventLoopGroup
        EventLoopGroup group = new DefaultEventLoopGroup(2);
        new ServerBootstrap()
                // boss 和 worker
                // 细分1:boss 只负责 ServerSocketChannel 上 accept 事件     worker 只负责 socketChannel 上的读写
                .group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug(buf.toString(Charset.defaultCharset()));
                                ctx.fireChannelRead(msg); // 让消息传递给下一个handler
                            }
                        })
                        .addLast(group, "handler2", new ChannelInboundHandlerAdapter() {
                            @Override                                         // ByteBuf
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf buf = (ByteBuf) msg;
                                log.debug(buf.toString(Charset.defaultCharset()));
                            }
                        });
                    }
                })
                .bind(8080);
    }
}

 结论:可以看到,nio 工人和 非 nio 工人也分别绑定了 channel(LoggingHandler 由 nio 工人执行,而我们自己的 handler 由非 nio 工人执行)

 handler 执行中如何换人?

友情提示;针对上面的添加的非Nio工人,是怎么实现handler换人的。

从源码: io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead()

源码:

    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRead(m);
        } else {
            executor.execute(new Runnable() {
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }

    }

 说明:

  • 如果两个 handler 绑定的是同一个线程,那么就直接调用
  • 否则,把要调用的代码封装为一个任务对象,由下一个 handler 的线程来调用

EventLoop

可以理解为线程池+Selector,可以关联多个Channel.

事件循环对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

越来越没意思

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值