Netty 核心组件

本文详细解析了Netty中的Bootstrap启动类、Channel通道组件、Pipeline流水线和Handler处理器,涵盖了ServerBootstrap配置、EventLoopGroup、Nio通道类型、Pipeline装配以及ChannelOption设置等内容,帮助理解Netty的异步非阻塞网络编程模型。
摘要由CSDN通过智能技术生成

Netty 核心组件

Bootstrap 启动类

Bootstrap类是Netty提供的一个工程类。基于它来完成Netty的客户端或者服务器端的Netty组件的组装,以及Netty程序的初始化。

在Netty在,有两个启动器类。分布对于服务器和客户端。

image-20220510161037522

启动流程
  • 创建一个服务器端的启动器

    ServerBootstrap bootstrap = new ServerBootstrap();
    
  • 创建反应器线程组 EventLoopGroup

    // 创建 反应器线程组 bossGroup和 workerGroup
    // bossLoopGroup 只处理连接请求,与客户端逻辑业务处理,交给workerGroup完成
    EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
    EventLoopGroup workLoopGroup = new NioEventLoopGroup();
    
  • 设置反应器线程组,不一定必须配置两个线程组,也可以仅配置一个EventLoopGroup反应器线程组。

    单个线程组,存在新连接的接受被更耗时的数据传输或者业务处理所阻塞。

    bootstrap.group(bossLoopGroup, workLoopGroup)
    
  • 设置nio类型的通道 channel

    Netty不止支持Java NIO,也支持阻塞式的OIO

    bootstrap.channel(NioServerSocketChannel.class) // 设置nio类型的通道
    
  • 设置监听端口

    bootstrap.localAddress(new InetSocketAddress(port))
    
  • 设置通道参数(ChannelOption

    bootstrap.option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列得到连接个数
    bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true) // 设置保持活动连接状态
    
  • 装配子通道的Pipeline流水线

    每个通道的子通道,都用一条ChannelPipeline流水线,内部有一个双向链表。

    装配流水线的方式是:将业务处理器ChannelHandler实例加入双向链表中。

    装配子通道等待Handler流水线调用childHandler()方法,传递一个ChannelInitializer通道初始化的实例。在父通道成功接收一个连接,并创建一个子通道后,就会初始化子通道,这里配置的ChannelInitializer实例会被调用。实例中有一个initChannel初始化方法,在子通道创建后会被执行,向子通道流水线增加1业务处理器。

    // 装配子通道流水线
    bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
        // 有连接到达时候会创建一个通道
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            // 向子通道流水线添加一个handler处理器
            socketChannel.pipeline().addLast(new NettyServerHandler());
        }
    }); // 给 workerGroup 的 EventLoop 对应的管道设置处理器
    
  • 开始绑定端口,通过调用sync同步方法阻塞直到成功

    ChannelFuture channelFuture = bootstrap.bind().sync();
    
  • 等待通道关闭的异步任务结束,服务监听通过会一直等待通道的异步任务结束

    channelFuture.channel().closeFuture().sync();
    
  • 关闭EventLoopGroup,释放所有资源。

    关闭Reactor反应器线程组,同时会关闭内部的subReactor子反应器,也会关闭内部的Selector选择器,内部的轮询线程及负责查询的所有子通道。在子通道关闭后,会释放底层资源,例如TCP Socket 文件描述符

    bossLoopGroup.shutdownGracefully();
    workLoopGroup.shutdownGracefully();
    

Channel 通道组件

Netty不直接使用Java NIO中的Channel通道组件,对Channel通道组件进行了封装。对于每一种通信协议都实现了自己的通道。

除了Java的NIO,Netty还能处理Java 面向流OIO。即Netty中每一种协议通道,都有NIO和OIO两个版本。

  • NioSocketChannel

    异步非阻塞TCP Socket传输通道

  • NioServerSocketChannel

    异步非阻塞TCP Socket服务器端监听通道

  • NioDatagramChannel

    异步非阻塞的UDP传输通道

  • NioSctpServerChannel

    异步非阻塞的Sctp服务器端监听通道

  • OioSocketChannel

    同步阻塞式TCP Socket传输通道

  • OioServerSocketChannel

    同步阻塞式TCP Socket服务器端监听通道

  • OioDatagramChannel:

    同步阻塞式UDP传输通道

  • OioSctpChannel:

    同步阻塞式Sctp传输通道

  • OioSctpServerChannel

    同步阻塞式Sctp服务器端监听通道

AbstractChannel

几乎所有的通道实现类都继承了io.netty.channel.AbstractChannel通道的抽象类

protected AbstractChannel(Channel parent) {
    this.parent = parent;   // 父通道
    id = newId();
    unsafe = newUnsafe(); // 底层NIO通道,完成实际的IO操作
    pipeline = newChannelPipeline(); // 一条通道,拥有一条流水线
}

核心属性

  • pipeline属性,表示处理器的流水线。在对通道进行初始化时候,会将pipeline属性初始化为DefaultChannelPipeline的实例。

  • parent 属性,表示通道的父通道。对于连接监听通道(例NioServerSocketChannel)其父通道为null。

    对于传输通道(例NioSocketChannel)来说,其parent属性的值为接收到该连接的服务器连接监听通道。

常用方法

  • ChannelFutre connect(SocketAddress address)

    连接远程服务器

  • ChannelFutre bind(SocketAddress address)

    绑定监听地址,开始监听新的客户端连接。在服务器的新连接监听和接收通道使用。

  • ChannelFutre close()

    关闭通道连接,返回连接关闭的ChannelFuture异步任务,如果需要在连接正式关闭后执行其他操作,则需要为异步任务设置回调方法。或者调用ChannelFuture异步任务的sync()方法来阻塞当前线程,直到通道关闭的异步任务执行完毕。

  • Channel read()

    读取通道数据,并启动入站处理,返回通道自身用于链式调用。(从内部的Java NIO channel 通道中读取数据,然后启动内部的Pipeline流水线,开启数据读取的入站处理。)

  • ChannelFuture write(Object o)

    启动出站流程处理,把处理后得最终数据写到底层Java NIO通道。此方法的返回值为出站处理的异步处理任务。

  • Channel flush()

    将缓冲区的数据立即写入到对端,并不是每次write操作都是将数据直接写到对端,write操作的作用在大部分情况下仅仅是写入到操作系统的缓冲区,操作系统会根据缓冲区的情况,决定什么时候把数据写到对端。而执行flush()方法立即将缓冲区写到对端。

父子通道

Netty中,每一个NioSocketChannel通道所封装的Java NIO通道,再往下就对应为socket描述符。

理论上操作系统底层的socket描述符分为两类:

  • 连接监听类型

    连接监听类型的socket描述符,在服务器端,负责接收客户端的套接字连接。

    一个"连接监听类型"的socket描述符可以接受(Accept)成千上万的传输类的socket描述符。

    在Netty中,异步非阻塞的服务器端监听通道NioServerSocketChannel,封装在Linux底层的描述符,是"连接监听类型"的socket描述符。

  • 传输数据类型

    数据传输类的socket描述符负责传输数据。同一条TCP的socket传输链路。在服务器端和客户端都分别存在一个与之响应的数据传输类型的socket描述符。

    在Netty中,NioSocketChannel异步非阻塞TCP Socket传输通道,封装在Linux底层的描述符,是"数据传输类型"的socket描述符。

在Netty中,将有接受关系的NioServerSocketChannel和NioSocketChannel称为父子通道。

其中NioServerSocketChannel负责服务器连接监听和接受,被称为父通道(Parenet Channel),

对应每一个接收到的NioSocketChannel传输类通道,也叫子通道(Child Channel)。

EmbeddedChannel 嵌入式通道

模拟入站与出站操作,底层不进行实际的传输。不需要启动Netty服务器和客户端,可以使用作为ChannelHandler业务处理器的单元厕所。

Handler业务处理器

Reactor反应器模型中,反应器查询到IO事件后,分发到Handler业务处理器,通过Handler完成IO操作和业务处理。

整个IO环节包括

  1. 从通道读取数据包
  2. 数据包解码
  3. 业务处理
  4. 目标数据编码
  5. 把数据包写到通道

Netty分成了入站和出站两种类型去处理IO环节

  • 入站处理(数据包解码,业务处理)

    从Netty的内部(如通道)到ChannelInboundHandler入站处理器

  • 出站处理(目标数据编码,把数据包写入通道)

    从ChannelOutboundHandler出站处理器到Netty的内部(如通道)

ChannelInboundHandler 通道入站处理器

核心方法

image-20220512112928239

  • channelRegistered

    当通道注册完成后,Netty会调用fireChannelRegistered,触发通道注册事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelRegistered方法会被调用。

  • channelActive

    当通道激活完成后,Netty会调用fireChannelActive,触发通道激活事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelActive方法会被调用。

  • channelRead

    当通道缓冲区可读,Netty会调用fireChannelRead,触发通道可读事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelRead方法,会被调用。

  • channelReadComplete

    当通道缓冲区读完。Netty会调用fireChannelReadComplete, 触发通道读完事件。通道会启动该入站操作的流水线处理,在通道注册过的入站处理器Handler的channelReadComplete方法,会被调用到。

  • channelInactive

    当连接被断开或者不可以,Netty会调用fireChannelInactive,触发连接不可用事件。

  • exceptionCaught

    当通道处理过程发送异常时,Netty会调用fireExceptionCaught,触发异常捕获事件。

使用方法

​ 开发过程中,只需要继承ChannelInboundHandlerAdapter这个默认实现,重写需要的方法。

ChannelOutboundHandler 通道出站处理器

核心方法

con

  • bind

    监听地址(IP+端口)绑定:完成底层JAVA IO通道的IP地址绑定,如果使用TCP传输协议,这个方法用于服务器端。

  • connect

    连接服务端:完成底层Java IO通道的服务器端的连接操作,如果使用TCP传输协议,这个方法用于客户端。

  • write

    写数据到底层: 完成Netty通道向底层Java IO通道的数据写入操作,仅仅触发写入操作,并不是完成实际的数据写入操作。

  • flush

    腾空缓冲区中的数据,把这些数据写到对端。

  • read

    从底层读数据,完成Netty通道向Java IO通道的数据读取

  • disConnect

    断开服务器连接:断开底层Java IO通道的服务器端连接。如使用TCP传输协议,此方法主要用于客户端

  • close

    主动关闭通道: 关闭底层的通道,例如服务器端的新连接监听通道

使用方法

开发过程中,只需要继承ChannelOutoundHandlerAdapter这个默认实现,重写需要的方法。

Reactor 反应器

在反应器模式中,反应器会负责一个事件处理线程。不断轮询,通过Selector选择器查询注册过的IO事件(选择键)。如果查询到IO事件,则分发给Handler业务处理器。

Netty中反应器也有多个实现,关系Channel通道类。例如NioSocketChannel通道,Netty的反应器类为NioEventLoop。

EventLoopGroup线程组

Netty中的Reactor反应器模式,是类似多线程版本的反应器模式。

EventLoopGroup线程组就是一个多线程版本的反应器,其中的单个EventLoop线程就对应一个子反应器(SubReactor)。

在程序开发过程中,通常不使用单个EventLoop线程,而是使用EventLoopGroup线程组。构造函数中存在一个参数用于指定内部线程数。构造器初始化时,会按照传入的线程数量,在内部构建多个Thread线程和多个EventLoop子反应器(一个线程对应一个EventLoop子反应器),进行多线程的IO事件查询和分发。如果没有指定内部线程数,默认的内部线程数为最大可用CPU处理器数量的2倍。(假设电脑使用的是4核的CPU,那么在内部会启动8个EventLoop线程,相当8个子反应器SubReactor实例)

为了及时接收(Accept)到新连接,在服务器端通常有两个独立的反应器。一个反应器负责新连接的监听和接受,一个反应器负责IO事件处理。对应到Netty中就是设置两个EventLoopGroup线程组,一个负责新连接监听和接受,一个负责IO事件处理。负责新连接监听和接受的EventLoopGroup线程组,负责查询父通道的IO事件,常命名为Boos线程组,另一个EventLoopGroup线程负责查询所有子通道的IO事件,并且执行Handler处理器中的业务处理常称为Work线程组。

Netty的Handler处理器分为两大类ChannelInboundHandler通道入站处理器和ChannelOutboundHandler通道出站处理器。都继承了ChannelHandler处理器接口。

image-20220510160220643

  • 入站处理
  • 出站处理

Pipeline 流水线

Netty中一个通道通常需要很多个Handler处理器来处理,实现方式是每个通道内部都有一个pipeline流水线将Handler装配起来。这里的pipeline是基于责任链模式的设计,底层是一个双向链表,支持动态的添加或者删除Handler。

  • 入站先添加Handler的先触发
  • 出站后添加Handler的先触发
ChannelOption通道选项

常见的通道选型

  • SO_RCVBUF, SO_SNDBUG

    TCP参数,每个TCP socket(套接字)在内核中都有一个发送缓冲区和一个接收缓冲区。通过这两个选型来设置TCP连接的这两个缓冲区大小。TCP全双工的工作模式以及TCP的滑动窗口便是依赖着两个独立的缓冲区以及其填充的状态。

  • TCP_NODELAY

    TCP参数,表示立即发送数据,Netty默认为True,而操作系统中默认为false。这个值用于设置Nagle算法的启用。设置True表示关闭Nagle算法

  • SO_KEEPALIVE

    TCP参数,表示底层TCP协议的心跳机制。true为连接保持心跳,默认为false。启用时,TCP会主动探测空闲连接的有效性,默认的心跳机制是7200s即2小时。Netty默认关闭此功能。

  • SO_REUSEADDR

    TCP参数,设置为true时表示地址复用,默认false。主要有以下情况需要使用

    • 当有一个相同本地地址和端口的socket1处于TIME_WAIT时,而我们希望启动的程序的socket2要占用该地址的端口,需要在重启服务且保持先前端口。
    • 有多块网卡或者用IP Alias技术的机器在同一端口启动多个进程,但每个进程绑定的本地IP地址不能相同。
    • 单个进程绑定相同的端口到多个socket(套接字)上,但每个socket绑定的IP地址不同
  • SO_LINGER

    TCP参数,表示关闭socket的延迟时间,默认值为-1, 表示禁用该功能。

    • -1表示socket.close()立即返回,但操作系统底层会将发送缓冲区全部发送到对端。
    • 0表示socket.close()立即返回,操作系统放弃发送缓冲区的数据,直接向对端发送RST包,对端手动复位错误。
    • 非0整数值表示调用socket.close()方法的线程被阻塞,直到延迟时间到来,发送缓冲区的数据发送完毕,若超时。则对端会收到复位错误。
  • SO_BACKLOG

    TCP参数,表示服务器端接收连接的队列长度,如果队列已满,客户端连接将被拒绝。

    默认在windows中为200,其他操作系统为128。如果连接建立频繁,服务器处理相信连接比较慢,可以适当调大这个参数。

  • SO_BROADCAST

    TCP参数,表示设置成广播模式。

ByteBuf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值