netty的常用类以及执行流程

在典型的情况下,一个监听连接的 EventLoop 会负责管理一个 ServerSocketChannel。这个 ServerSocketChannel 用于监听指定端口上的连接请求,并在有新的连接请求到达时接受连接,然后创建对应的 SocketChannel。

当服务器监听端口时,会创建一个 ServerSocketChannel,并将其注册到 EventLoop 上。当有新的连接到来时,ServerSocketChannel 就会触发连接事件,EventLoop 就会调用相应的回调方法来处理这个连接事件,最终将其转化为一个新的 SocketChannel,表示与客户端的连接。这个 SocketChannel 会被注册到另一个 EventLoop 上,用于处理后续的数据读写事件。

bossGroup:负责接收客户端的连接请求,并将连接分配给 Worker 线程处理。通常情况下,bossGroup 只会包含一个 EventLoop。

workerGroup:负责处理网络 I/O 事件,例如读写数据。workerGroup 可以包含多个 EventLoop。
一旦连接建立后,该连接的处理就与 bossEventLoop 无关了。bossEventLoop 主要负责接收连接请求,将连接分配给 workerEventLoop,然后由 workerEventLoop 负责处理连接上的所有消息事件。

操作系统的传输层将数据包交给相应的应用程序端口,并触发相应的网络事件。
网络事件被封装为事件对象,然后传递给相应的 Channel。
Channel 将接收到的事件放入到与之关联的 EventLoop 的事件队列中。
EventLoop 从事件队列中获取事件,并调用相应的 ChannelHandler 处理事件。
因此,消息的传输过程是通过操作系统和 Channel 来进行的,Channel 负责将事件放入到相应的 EventLoop 的事件队列中,而 EventLoop 则负责处理事件队列中的事件。

在 Netty 中,每个 EventLoop 都运行在一个单独的线程上。EventLoop 负责处理该线程上的所有任务,包括处理网络事件、执行定时任务等。
我们开始可以指定worker的eventloop的数量参数。
具体来说,EventLoopGroup 在内部维护一个线程池,用于创建和管理 EventLoop 实例。当创建 EventLoop 实例时,EventLoopGroup 会从线程池中获取一个空闲的线程,并将 EventLoop 分配给该线程运行。这样,每个 EventLoop 就运行在自己的线程上,负责处理其关联的 Channel 上的事件。

总体概述

类关系

给ServerBootstrap配置两个EventLoopGroup,一个建立连接,一个处理网络io。
EventLoopGroup给EventLoop分配线程。
在 Netty 中,EventLoop 通过不断轮询 Selector 来检测 Channel 上发生的事件,当 Channel 上的事件到达时,EventLoop 会将事件传入 相应的Channel 的成员变量 ChannelPipeline 中,经过所有ChannelHandler 来处理这些事件。

执行流程

当一个事件传入时,会判断是连接事件还是其他事件,如果是连接事件交给eventloopgroup中的建立连接的eventloop,其中的nioserverchannel会处理连接事件,如果是其他事件,那么会交给处理网络io的eventloop中的channel,eventloop轮询发现有channel有事件需要执行,那么就在这个eventloop线程中调用channel的channelpipeline,执行相应的channelhander。

其中:eventloopgroup可以指定eventloop的数量,eventloopgroup就像线程池一样,会负载均衡地分配建立连接的channel给不同的eventloop。

当数据包到达 Bootstrap 中配置的 ServerBootstrap(或 Bootstrap)所关联的 Channel 时,EventLoopGroup 决定将该 Channel 注册到哪个 EventLoop 上处理事件。
具体过程如下:

  1. 当 ServerBootstrap.bind() 方法被调用时,它会创建一个新的 Channel 实例,该实例将被注册到 EventLoopGroup。
  2. EventLoopGroup 会选择一个适当的 EventLoop,通常采用一种简单的负载均衡策略,以确保各个 EventLoop 被平均利用。这样,不同的 Channel 就可能被分配到不同的 EventLoop 上。
  3. 选定的 EventLoop 将负责处理该 Channel 的所有事件,包括接收数据、处理业务逻辑以及发送响应。

当NioServerSocketChannel检测到连接请求时,会触发连接的接受,并为每个新连接创建一个Channel。这个Channel会被传递给ChannelInitializer,并由它负责初始化Channel的pipeline。

Netty的NIO的常用概念

服务端evenloop线程使用一个 NioServerSocketChannel 实例来接受新的连接请求。
对于 多线程的bossEventLoopGroup,通常 EventLoop 会共享一个 NioServerSocketChannel 实例,而不是为每个 EventLoop 创建一个独立的实例。

NioServerSocketChannel 用于监听新的连接请求,而每个新连接都会被封装成一个 NioSocketChannel 对象,然后被注册到 workerGroup 中的一个 EventLoop 上进行处理。每个 NioSocketChannel 对应一个已建立连接的客户端,而 workerGroup 中的 EventLoop 负责处理这些连接上的数据读写事件。

ServerBootstrap

ServerBootstrap 是一个用于帮助配置和启动服务器的类。它是Netty中用于创建服务器端应用程序的主要入口点
bind 方法用于绑定服务器端口。调用 sync 方法等待绑定操作完成。
ChannelFuture channelFuture = serverBootstrap.bind().sync();

重要成员变量

group:保存 bossGroup 和 workerGroup,即接受连接和处理连接的 EventLoopGroup。
channelFactory:保存用于创建接受连接的 Channel 的 ChannelFactory。
childHandler:保存连接被接受后的处理器,即用于配置 ChannelPipeline 的 ChannelInitializer。
childOptions:保存子连接参数的 Map,用于设置子连接的参数。
handler:保存用于配置 ServerChannel 的处理器。通常用于设置 ServerChannel 上的一些全局处理逻辑。

重要方法

group(EventLoopGroup bossGroup, EventLoopGroup workerGroup):设置接受连接和处理连接的 EventLoopGroup。bossGroup 用于接受传入的连接请求,而 workerGroup 用于处理已经被接受的连接。
channel(Class<? extends Channel> channelClass):设置用于接受连接的 Channel 类型。例如,NioServerSocketChannel 表示使用 NIO 的方式处理传入的连接。
childHandler(ChannelInitializer childHandler):设置连接被接受后的处理器,用于配置 ChannelPipeline,添加自定义的处理器链。
bind():绑定服务器并返回一个 ChannelFuture,用于同步等待绑定操作完成。
option(ChannelOption option, T value):设置主连接参数,例如 ChannelOption.SO_BACKLOG。
childOption(ChannelOption childOption, T value):设置子连接参数,例如 ChannelOption.SO_KEEPALIVE。

工作流程

它的工作流程涉及到一系列步骤,从配置 EventLoopGroup 到绑定端口,再到设置 ChannelInitializer 和处理器链。以下是 ServerBootstrap 的工作流程的主要步骤:

  1. 创建 ServerBootstrap 实例:ServerBootstrap serverBootstrap = new ServerBootstrap();

  2. 设置 EventLoopGroup:
    设置用于接受连接和处理连接的 EventLoopGroup。通常,一个用于接受连接的 bossGroup,和一个用于处理连接的 workerGroup。
    serverBootstrap.group(bossGroup, workerGroup);

  3. 设置 Channel 类型:
    设置用于接受连接的 Channel 类型,例如使用 NIO 的 NioServerSocketChannel。
    serverBootstrap.channel(NioServerSocketChannel.class);

  4. 设置连接参数和子连接参数:
    使用 option 和 childOption 方法设置连接参数和子连接参数。
    serverBootstrap.option(ChannelOption.SO_BACKLOG, 128)
    .childOption(ChannelOption.SO_KEEPALIVE, true);

  5. 设置连接处理器:
    使用 childHandler 方法设置连接被接受后的处理器,即用于配置 ChannelPipeline 的 ChannelInitializer。
    serverBootstrap.childHandler(new ChannelInitializer() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    // 添加处理器到 ChannelPipeline
    ch.pipeline().addLast(new MyServerHandler());
    }
    });

  6. 绑定端口:
    调用 bind 方法绑定服务器端口,并返回一个 ChannelFuture。
    ChannelFuture channelFuture = serverBootstrap.bind(8080);
    同步等待绑定完成:

  7. 调用 sync 方法同步等待绑定操作完成。
    channelFuture.sync();
    阻塞主线程:

  8. 调用 channelFuture.channel().closeFuture().sync() 阻塞主线程,等待服务器关闭。
    channelFuture.channel().closeFuture().sync();
    关闭 EventLoopGroup:

  9. 在服务器关闭后,调用 shutdownGracefully 关闭 EventLoopGroup。
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();

Channel

在计算机网络编程中,Channel(通道) 是一种抽象概念,代表着数据在源和目标之间的连接。通道是数据传输的通路,通道的两端可以是网络连接、文件、套接字等。

Channel类型

在Netty中,一个连接(Connection)通常对应一个 Channel。Channel 是Netty中的一个核心抽象,它代表了一个开放的连接,可以用于进行数据的读写操作。每个 Channel 对象都与一个底层的网络连接相关联,而这个网络连接通常对应着一个客户端与服务器之间的TCP连接。

在Netty中,Channel 与具体的传输协议无关,因此它可以用于处理各种类型的协议,例如TCP、UDP、WebSocket等。一个 Channel 可以用于双向通信,即在一个连接上可以同时进行读和写操作。

每个 Channel 都会注册到一个 EventLoop 上,而 EventLoop 负责处理该 Channel 上的所有事件。这种模型使得在高并发环境中,多个连接可以并行地由不同的 EventLoop 处理,而不会相互阻塞。

  • 文件通道 (FileChannel): 用于对文件进行读写操作。
  • 套接字通道 (SocketChannel, ServerSocketChannel, DatagramChannel): 用于进行网络通信,这些通道是基于 Java NIO 提供的 SelectableChannel 的实现。它们通常通过 EventLoop、ChannelPipeline 和自定义的处理器来实现异步和事件驱动的网络编程。

SocketChannel 是用于 TCP 客户端通信的通道。
它可以连接到远程服务器,并进行双向通信。
SocketChannel 支持阻塞和非阻塞的模式。

ServerSocketChannel 是用于 TCP 服务器通信的通道。
它监听客户端的连接请求,并创建一个新的 SocketChannel 用于与客户端通信。
ServerSocketChannel 也支持阻塞和非阻塞的模式。

DatagramChannel 是用于 UDP 通信的通道。
UDP 是面向消息的,DatagramChannel 支持无连接的数据报传输。
DatagramChannel 与 SocketChannel 相比更轻量,适用于不需要可靠性传输的场景。

  • 管道 (Pipe.SinkChannel, Pipe.SourceChannel): 用于在两个线程之间进行通信。

通道(Channel)和流(Stream)有一些相似之处,但也有一些关键的区别。

相似之处

数据传输: 通道和流都用于在程序和数据源/目的地之间进行数据传输。
字节流和字符流: 通道和流都可以用于处理字节数据(字节流)和字符数据(字符流)。

区别
阻塞非阻塞

通道: 通道通常是非阻塞的,可以使用选择器(Selector)来实现多路复用,从而监控多个通道的状态。
流: 流通常是阻塞的,即在读写操作时,如果没有数据可读或没有足够的空间可写,程序会阻塞等待。

双向性

通道: 通道是双向的,可以支持读和写操作。例如,文件通道 (FileChannel) 可以同时支持读取和写入文件。
流: 流通常是单向的,即要么是输入流(从数据源读取数据),要么是输出流(向数据源写入数据)。需要使用两个流才能实现双向传输。

底层实现

通道: 通道通常直接映射到操作系统的底层 I/O 操作,因此性能可能更好。在 Java NIO 中,通道与 Selector 搭配使用,可以实现非阻塞 I/O。
流: 流通常是在通道的基础上建立的高级抽象,提供了更高层次的 API。在 Java IO 中,流是基于字节或字符的 I/O 操作。

直接缓冲区

在 Java NIO 中,通道(Channel)支持直接缓冲区(DirectBuffer),这使得数据可以直接从内存中的缓冲区传输到通道,或者从通道直接传输到内存中的缓冲区,而不需要中间步骤。
通道: 通道支持直接缓冲区,允许将数据直接从内存中的缓冲区传输到通道,而不需要中间步骤。
流: 流通常不支持直接缓冲区,需要通过中间的数组或缓冲区来进行数据传输。

通常有两种类型的缓冲区:堆缓冲区(Heap Buffer)和直接缓冲区(Direct Buffer)。
堆缓冲区: 在堆上分配的缓冲区,数据位于Java堆内存中。这是默认的缓冲区类型。
直接缓冲区: 直接分配在操作系统的内存中,而不是Java堆中。直接缓冲区可以通过 ByteBuffer.allocateDirect() 方法来创建。
直接缓冲区的优势之一是可以通过 FileChannel 的 transferTo() 和 transferFrom() 方法直接在通道之间传输数据,而无需通过中间缓冲区。
当使用非直接缓冲区(Heap Buffer)时,通常的操作是先将数据从通道读取到堆缓冲区,然后再进行其他操作。同样,当写入时,数据也会从堆缓冲区写入到通道。

Channel内部有成员变量unsafe和Pipeline,unsafe是Channel网络io的底层实现,ChannelPipeline 负责管理该 Channel 上的处理器链。

SelectionKey

简单源码

public interface SelectionKey {
    // 表示对读事件感兴趣
    static final int OP_READ = 1;
    // 表示对写事件感兴趣
    static final int OP_WRITE = 4;
    // 表示对连接事件感兴趣
    static final int OP_CONNECT = 8;
    // 表示对接受事件感兴趣
    static final int OP_ACCEPT = 16;

    // 返回与此键关联的通道
    SelectableChannel channel();

    // 返回选择器
    Selector selector();

    // 返回表示感兴趣的事件的操作集合
    int interestOps();

    // 设置感兴趣的事件的操作集合
    SelectionKey interestOps(int ops);

    // 返回表示已准备就绪的操作的操作集合
    int readyOps();
    。。。。。。。

}

ChannelPipline

ChannelPipline是ChannelHandler的容器,维护了一个Handler的链表和迭代器。
当事件发生时,在eventloop的中它会被传递给ChannelPipeline,然后从头到尾依次经过每个ChannelHandler。
使用ServerBootstrap启动服务器netty会自动为每个Channel创建一个独立的pipepline,我们只要将想用的ChannelHandle加入即可。

ChannelHandler

事务的执行是通过channelpipeline,连接,读写等网络io操作是由channelpipeline的Handler执行(Handler中调用unsafe的方法)。

Eventloop

Eventloop作为NIO框架的Reactor线程,其中有一个Select的成员变量,用来实现多路复用。
Channel需要注册到EventLoop的多路复用器上,EventLoop本质上就是处理网络读写任务的Reactor线程,在Neety中,它还可以用力啊处理定时任务和用户自定义NioTask任务。
Eventloop的run方法是其核心,伪代码:

public void run() {
    while (true) {
        try {
            // 阻塞直到有事件发生
            int readyChannels = selector.select();

            // 处理所有已经就绪的事件
            if (readyChannels > 0) {
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                for (SelectionKey key : selectedKeys) {
                    if (key.isAcceptable()) {
                        // 处理连接事件
                        // 处理连接事件的逻辑
                    } else if (key.isReadable()) {
                        // 处理读事件
                        // 处理读事件的逻辑
                    } else if (key.isWritable()) {
                        // 处理写事件
                        // 处理写事件的逻辑
                    }
                    // 可以处理更多类型的事件,根据实际需要扩展
                }
                selectedKeys.clear();
            }

            // 处理任务队列中的任务
            while (!tasks.isEmpty()) {
                Runnable task = tasks.poll();
                task.run();
            }
        } catch (IOException e) {
            // 处理异常情况
            e.printStackTrace();
        }
    }

EventGroup

EventLoopGroup 负责管理一组 EventLoop 实例,每个 EventLoop 实例与一个线程相关联。
EventLoopGroup 提供了 shutdownGracefully() 方法,用于优雅地关闭所有关联的 EventLoop。
服务端启动时候,创建了两个NioEventLoopGroup,他们是两个独立的Reactor线程池,一个用来接收客户端的TCP连接,另一个用来处理IO相关的读写操作,或者执行系统Task,定时任务Task等。
接收客户端请求的线程池职责

  1. 接收客户端的TCP连接,初始化Channel参数。
  2. 将链路状态变更事件通知给ChannelPipeline。

处理IO操作的线程池的职责
3. 异步读取通信数据包,发送读事件给ChannelPipeline;
4. 异步发送消息,通过发送读事件给ChannelPipeline。
5. 执行系统Task和定时Task;

Netty中事件触发和处理的一般流程

Channel注册和初始化: 当一个Channel被创建时,它会被注册到一个EventLoop中,并被分配一个ChannelPipeline。

ChannelPipeline初始化: 每个Channel都有一个关联的ChannelPipeline,它是一个Handler链。在Channel创建后,可以通过ChannelPipeline添加各种ChannelHandler,这些Handler将按照添加的顺序组成一个处理链。

事件触发: 当Channel中发生某些事件时,例如连接建立、数据接收等,Netty会将相应的事件封装成对象,并触发相应的事件方法。

事件传播: 事件从ChannelPipeline的头部传播到尾部,也就是从入站事件处理到出站事件处理。

Handler的处理逻辑执行: 在事件传播的过程中,每个Handler都有机会处理该事件。Handler需要实现特定的接口或者继承特定的抽象类,以便处理相应的事件类型。

ChannelHandlerContext: 在处理事件时,Handler可以使用ChannelHandlerContext与其所在的ChannelPipeline和其他Handler进行交互。它提供了访问Pipeline和其他上下文信息的方法。

事件的传递和截断: Handler在处理完事件后可以选择将事件继续传播到下一个Handler,也可以通过截断传播链来阻止事件传播。这允许开发人员对事件进行更复杂的控制和定制。

异常处理: 如果在事件处理过程中发生异常,Netty会负责处理异常。通常,异常会传播到Pipeline的尾部,并由特定的异常处理器进行处理。

Channel关闭: 当Channel被关闭时,Netty也会触发相应的事件,允许Handler执行必要的清理工作。

Promise和Future

Promise 是 Future 的扩展,它继承了 Future 接口。它们都代表了一个尚未完成的操作,可以用于异步操作的结果通知和处理。
区别:

可写性: Future 是只读的,一旦创建就不能被修改。而 Promise 是可写的,可以通过它来设置操作的结果。
操作结果的设置: 在使用 Future 时,你只能等待其完成,而不能主动设置操作的结果。而在使用 Promise 时,你可以主动设置操作的结果,因此它提供了更灵活的控制。
用途: Future 通常用于表示一个异步操作的结果,而 Promise 用于表示一个异步操作的开始和结果的产生。在很多情况下,你会首先创建一个 Promise,然后将它转化为一个 Future 对象,传递给其他部分的代码,使得它们可以等待异步操作的结果。

建立连接到接收数据的serverbootstarp的两个线程池的工作

bossGroup: bossGroup 的 EventLoop 负责接受传入的连接请求。当有新的连接到达时,EventLoop 会产生一个 Channel 对象,并将该连接的处理委托给这个 Channel。这个 Channel 就是通过 ChannelFactory 创建的。在这一阶段,这个 Channel 还没有被注册到 workerGroup 的 EventLoop 上,而是由 bossGroup 的 EventLoop 管理。

workerGroup: 一旦连接被 bossGroup 接受,它会将连接注册到 workerGroup 中的某个 EventLoop 上。这样,workerGroup 中的 EventLoop 就会负责处理该连接的后续事件,例如数据读写、处理业务逻辑等。

两个ChannelPipeline

Netty 中确实存在两个不同的 ChannelPipeline,分别用于 bossGroup 和 workerGroup。

bossGroup 的 ChannelPipeline:
在 bossGroup 的 EventLoop 中,每个接受到的连接都会有一个自己的 Channel,对应一个独立的 ChannelPipeline。这个 ChannelPipeline 主要用于处理连接的建立阶段,通常只包含一些用于连接接受的处理器,例如 ChannelInitializer 中的处理器。这个阶段的处理主要是连接的接受和注册。

workerGroup 的 ChannelPipeline:
一旦连接被 bossGroup 接受,连接就会被注册到 workerGroup 中的某个 EventLoop 上。这时,连接的 ChannelPipeline 就会变为 workerGroup 的 ChannelPipeline。这个 ChannelPipeline 将包含所有用于处理业务逻辑的处理器,例如数据读写、业务逻辑处理等。

selector和异步和channel

当一个 Channel 注册到 Selector 上时,底层的操作系统会负责监视该 Channel 上的事件。当事件发生时,操作系统会通知 Netty,Netty 将这些事件封装并添加到 EventLoop 的任务队列中等待处理。这个过程是异步的,因为它是由操作系统在事件发生时异步地通知 Netty。

而在 EventLoop 中,Selector 的轮询过程是同步的。EventLoop 会不断地调用 Selector 的 select() 方法来检测是否有事件发生。当有事件发生时,EventLoop 将从 Selector 中获取事件,并根据事件类型进行处理。这个过程是同步的,因为它是由 EventLoop 主动地调用 Selector 的方法来获取事件。

综上所述,虽然从操作系统获得事件是异步的,但是 Selector 获得事件的过程是同步的,因为它是由 EventLoop 主动地调用 Selector 的方法来获取事件。

  • 33
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值