Netty的基础知识

Netty对NIO进行的封装

  1. 设计优雅:适用于各种传输类型的统一(API阻塞和非阻塞Socket);基于灵活可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型(单线程,一个或多个线程池)

  2. 适用方便:详细记录的Javadoc,用户指南和示例,没有其他依赖项

  3. 高性能、吞吐量更高:延迟更低,减少资源消耗,最小化不必要的内存复制

  4. 安全:完整的SSL/TLS和StartTLS支持

不同的线程模型

  1. 不同的线程模型,对程序的性能有很大的影响,目前存在的线程模型有:传统的阻塞IO服务模型、Reactor模式。

  2. 根据Reactor的数量和处理资源线程池的数量不同,有3种典型的实现:

    • 单Reactor单线程
    • 单Reactor多线程
    • 主从Reactor多线程
  3. Netty线程模型(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)

理解Reactor

在传统的IO阻塞编程模型中,增加线程池,就构成了Reactor模式的基本设计思想。可以用如下的方式理解Reactor:

  1. Reactor模式是通过一个或多个输入同时传递给服务处理器的模式(基于事件驱动)

  2. 服务器端程序处理传入的多个请求,并将它们分派到相应的处理线程,因此Reactor模式也叫Dispatcher模式

  3. Reactor模式使用IO复用监听

Reactor模式中核心组成:

  1. Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理器程序来对IO事件作出反应。

  2. Handlers:处理程序执行IO事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor通过调度适当的处理程序来响应IO事件,处理程序执行非阻塞操作。

理解主从Reactor多线程:

  1. Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件

  2. 当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor

  3. SubReactor将连接加入到连接队列进行监听并创建handler进行各种事件处理

  4. 当有新事件发生时,SubReactor就会调用对应的handler处理

  5. handler通过Read取数据,分发给后面的worker线程处理

  6. worker线程池分配独立的worker线程进行业务处理,并返回结果

  7. handler收到响应的结果后,再通过send将结果返回给client

  8. Reactor主线程可以多赢多个Reactor子线程,即MainReactor可以关联多个SubReactor

理解Netty中的BoosGroup和WorkerGroup

  1. Netty抽象出两组线程池:BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络的读写

  2. BossGroup和WorkerGroup类型都是NioEventLoopGroup

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

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

  5. NioEventLoopGroup可以有多个线程,即可以含有多个NioEventLoop

  6. 每个Boss NioEventLoop执行的步骤有3步

    1. 轮询accept事件

    2. 处理accept事件,与client建立连接,生成NioScocketChannel,并将其注册到某个worker NIOEventLoop上的selector中

    3. 处理任务队列的任务,即runAllTasks

  7. 每个Worker NIOEventLoop循环执行的步骤

    1. 轮询read、write事件

    2. 处理IO事件,即read、write事件,在对应NioSocketChannel处理

    3. 处理任务队列的任务,即runAllTasks

  8. 每个Worker NIOEventLoog处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获取到对应的通道。

2021-07-24-10-20-14

BoosGroup和WorkerGroup干什么的

  1. BossGroup程维护Selector,只关注Accecpt当接收到Accept事件,获取到对应的SocketChannel,封装成NIOScoketChannel并注册到Worker线程(事件循环),并进行维护

  2. 当Worker线程监听到selector中通道发生自己感兴趣的事件后,就进行处理(就由handler),注意handler已经加入到通道

2021-07-24-10-27-56

异步模型

  1. Netty中的IO操作是异步的,包括Bind、Write、Connect等操作会简单的返回一个ChannelFuture。

  2. 调用者并不能立刻获得结果,而是通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

  3. Netty的异步模型是建立在future和callback的之上的,callback就是回调。重点说Future,它的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不合适。那么可以在调用fun的时候,立马返回一个Future,后续可以通过Future去监控方法fun的处理过程(即:Future-Listener机制)

Netty与异步模型:

  1. 在使用Netty进行编程时,拦截操作和转换出入站数据只需要提供callback或利用future即可。这使得链式操作简单、高效,并且有利于编写可重用的、通用的代码。

  2. Netty框架的目标就是让业务逻辑从网络基础应用编码中分离处理,解脱出来。

2021-07-26-15-08-16

TaskQueue

任务队列中的Task有3种典型的使用场景(这部分我不是很理解):

  1. 用户程序自定义的普通任务
  2. 用户自定义定时任务
  3. 非当前Reactor线程调用Channel的各种方法

例如在推送系统的业务线程里面,根据用户的标识,找到对应的Channel应用,然后调用Write类方法想该用户推送消息,就会进入到这种场景。最终的Write会提交到任务队列中被异步消费。

Future-Listener机制

  1. 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作。

  2. 常见的操作如下:

  • 通过isDone方法来判断当前操作是否完成

  • 通过isSuccess方法来判断已经完成的操作是否成功

  • 通过getCause方法来获取已经完成的当前操作失败的原因

  • 通过isCancelled方法来判断已完成的当前操作是否被取消

  • 通过addlistener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果Future对象已完成,则通知指定的监听器

Bootstrap和ServerBootstrap

  1. Bootstrap的意思是引导,一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中的Bootstrap类是客户端程序的引导类,ServerBootstrap是服务端启动引导类。

  2. 常用的方法有:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 设置EventLoop
public B group(EventLoopGroup group);
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup);

// 设置一个服务端的通道实现
public B channel(Class<? extends C> channelClass);

// 用来给ServerChannel添加配置
public <T> B option(ChannelOption<T> option, T value);

// 用来给接受到的通道添加配置
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value);

// 用来设置业务处理器类
public ServerBootstrap childHandler(ChannelHandler childHandler);

// 用来设置绑定的端口
public ChannelFuture bind(int inetPort);

// 用来链接服务器
public ChannelFuture connect(String inetHost, int inetPort);

Future和ChannelFuture

  1. 常用方法有:
1
2
3
4
5
6
7

// 返回当前正在进行IO操作的通道
Channel channel();

// 等待异步操作执行完毕
ChannelFuture sync();

Channel

  1. Netty网络通信的组件,能够用于执行网络IO操作。

  2. 通过Channel可获得当前网络连接的通道的状态

  3. 通过Channel可获得网络连接的配置参数(例如接收缓冲区大小)

  4. Channel提供异步的网络IO操作(如建立连接,读写,绑定端口),异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成

  5. 调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以IO操作成功、失败或取消时回调通知调用方

  6. 支持关联IO操作与对应的处理程序

  7. 不同的协议、不同的阻塞类型的链接都有不同的Channel类型与之对应,常用的Channel类型有:

    • NioSocketChannel,异步的客户端TCP Socket连接
    • NioServerSocketChannel,异步的服务TCP Socket连接
    • NioDatagramChannel,异步的UDP连接
    • NioSctpChannel,异步的客户端Sctp连接
    • NioSctpServerChannel,异步的Sctp服务器端连接,这些通道涵盖了UDP和TCP网络IO以及文件IO

Selector

  1. Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件。

  2. 当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询这些注册的Channel是否有已就绪的IO事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个Channel

ChannelHandler及其实现类

  1. ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序(我似乎目前没有看到转发的操作)。

  2. ChannelHandler本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类

  3. 实现类一览:

    • ChannelInboundHandler:
    • ChannelOutboundHandler:
    • ChannelInBoundHandlerAdapter:
    • ChannelOutboundHandlerAdapter:

Netty的组件设计

  1. Netty的主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。

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

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

Pipeline与ChannelPipeline

  1. 在Netty中,每个Channel都有且仅有一个ChannelPipeline与之对应,它们的组成关系如下:

2021-07-26-15-58-12

一个Channel包含了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个ChannelHandlerContext中又关联着一个ChannelHandler。

入栈事件和出站事件在一个双向链表中,入栈事件会从链表的head往后传递到最后一个入栈的handler,出站事件会从链表的tail往前专递到最前一个出站的handler,两种类型的handler互不干扰。

ChannelPipeLine的重点知识:

  1. ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及Channel中各个的ChannelHandler如何相互交互(我们还可以控制控制各个ChannelHandler如何交互,这是我没有想到的)

ChannelHandlerContext

  1. 保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象

  2. 即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用.

  3. 常用方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

// 关闭通道
ChannelFuture close();

// 刷新
ChannelOutboundInvoker flush();

// 将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler(出站)(感觉理解的不是很透彻哦)
ChannelFuture writeAndFlush(Object msg);

ChannelOption

  1. Netty在创建Channel实例后,一般都需要设置ChannelOption参数。

  2. ChannelOption参数如下:

    • ChannelOption.SO_BACKLOG

      对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。

    • ChannelOption.SO_KEEPALIVE

      一直保持连接活动状态

EventLoopGroup及其实现类NioEventLoopGroup

  1. EventloopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个Eventloop同时工作,每个Eventloop维护着一个Selector实例。

  2. EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个Eventloop来处理任务。在Netty服务器端编程中,我们一般都需要提供两个EventloopGroup,如:BossEventloopGroup和WorkerEventloopGroup。

  3. 通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理。

Unpooled类

  1. Netty提供一个专门用来操作缓冲区(即Netty的数据容器)的工具类

  2. 常用方法:

1
2
3
4

// 通过给定的数据和字符编码返回一个ByteBuf对象
public static ByteBuf copiedBuffer(CharSequence string, Charset charset);

编码解码

  1. Netty提供的编码器

    • StringEncoder:对字符串进行编码
    • ObjectEncoder:对Java对象进行编码
  2. Netty提供的解码器

    • StringDecoder:对字符串进行解码
    • ObjectDecoder:对Java对象进行解码
  3. Netty提供的一系列实用的编解码器,都实现了ChannelInboundHandler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法都会被调用。随后,它被调用由解码器多提供的decode方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

  4. ByteToMessageDecoder直接继承于ChannelInboundHandlerAdapter。ByteToMessageDecoder会对入站数据进行缓冲,知道它准备好被处理(这个工作并不是ByteToMessageDecoder做的,而是实现decode方法是做的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) 
      throws Exception {

   if (in.readableBytes() >= 8) {
      out.add(in.readLong());
   }

}

  1. 无论是编码器或者解码器,接受的消息类型必须与待处理的消息类型一致,否则该handler不会被执行。

ReplayingDecoder

使用这个类我们不需要调用readableBytes,参数S指定了用户状态管理的类型,其中Void代表不需要状态管理(我该如何去理解这个状态管理?)。

ReplayingDecoder有一些局限性:

  1. 并不是所有的ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出一个UnsupportOperationException。

  2. ReplayingDecoder在某些情况下可能会鳗鱼ByteToMessageDecoder,例如网络缓慢并且消息格式复杂时,消息会被拆成多个碎片,速度变慢。

(我感觉我不需要关注这些技术,因为我极大可能性是使用Protobuf)

其他解码器

  1. LineBasedFrameDecoder:这个类在Netty内部也有使用,它使用行尾控制字符(\n或者\r\n)作为分隔符来解析数据。

  2. DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分隔符。

  3. HttpObjectDecoder:一个HTTP数据的解码器

  4. LengthFieldBasedFrameDecoder:通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息。