Netty对NIO进行的封装
-
设计优雅:适用于各种传输类型的统一(API阻塞和非阻塞Socket);基于灵活可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型(单线程,一个或多个线程池)
-
适用方便:详细记录的Javadoc,用户指南和示例,没有其他依赖项
-
高性能、吞吐量更高:延迟更低,减少资源消耗,最小化不必要的内存复制
-
安全:完整的SSL/TLS和StartTLS支持
不同的线程模型
-
不同的线程模型,对程序的性能有很大的影响,目前存在的线程模型有:传统的阻塞IO服务模型、Reactor模式。
-
根据Reactor的数量和处理资源线程池的数量不同,有3种典型的实现:
- 单Reactor单线程
- 单Reactor多线程
- 主从Reactor多线程
-
Netty线程模型(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)
理解Reactor
在传统的IO阻塞编程模型中,增加线程池,就构成了Reactor模式的基本设计思想。可以用如下的方式理解Reactor:
-
Reactor模式是通过一个或多个输入同时传递给服务处理器的模式(基于事件驱动)
-
服务器端程序处理传入的多个请求,并将它们分派到相应的处理线程,因此Reactor模式也叫Dispatcher模式
-
Reactor模式使用IO复用监听
Reactor模式中核心组成:
-
Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理器程序来对IO事件作出反应。
-
Handlers:处理程序执行IO事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor通过调度适当的处理程序来响应IO事件,处理程序执行非阻塞操作。
理解主从Reactor多线程:
-
Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
-
当Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
-
SubReactor将连接加入到连接队列进行监听并创建handler进行各种事件处理
-
当有新事件发生时,SubReactor就会调用对应的handler处理
-
handler通过Read取数据,分发给后面的worker线程处理
-
worker线程池分配独立的worker线程进行业务处理,并返回结果
-
handler收到响应的结果后,再通过send将结果返回给client
-
Reactor主线程可以多赢多个Reactor子线程,即MainReactor可以关联多个SubReactor
理解Netty中的BoosGroup和WorkerGroup
-
Netty抽象出两组线程池:BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络的读写
-
BossGroup和WorkerGroup类型都是NioEventLoopGroup
-
NioEventLoopGroup相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是NioEventLoop
-
NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网络通讯
-
NioEventLoopGroup可以有多个线程,即可以含有多个NioEventLoop
-
每个Boss NioEventLoop执行的步骤有3步
-
轮询accept事件
-
处理accept事件,与client建立连接,生成NioScocketChannel,并将其注册到某个worker NIOEventLoop上的selector中
-
处理任务队列的任务,即runAllTasks
-
-
每个Worker NIOEventLoop循环执行的步骤
-
轮询read、write事件
-
处理IO事件,即read、write事件,在对应NioSocketChannel处理
-
处理任务队列的任务,即runAllTasks
-
-
每个Worker NIOEventLoog处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获取到对应的通道。
BoosGroup和WorkerGroup干什么的
-
BossGroup程维护Selector,只关注Accecpt当接收到Accept事件,获取到对应的SocketChannel,封装成NIOScoketChannel并注册到Worker线程(事件循环),并进行维护
-
当Worker线程监听到selector中通道发生自己感兴趣的事件后,就进行处理(就由handler),注意handler已经加入到通道
异步模型
-
Netty中的IO操作是异步的,包括Bind、Write、Connect等操作会简单的返回一个ChannelFuture。
-
调用者并不能立刻获得结果,而是通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。
-
Netty的异步模型是建立在future和callback的之上的,callback就是回调。重点说Future,它的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不合适。那么可以在调用fun的时候,立马返回一个Future,后续可以通过Future去监控方法fun的处理过程(即:Future-Listener机制)
Netty与异步模型:
-
在使用Netty进行编程时,拦截操作和转换出入站数据只需要提供callback或利用future即可。这使得链式操作简单、高效,并且有利于编写可重用的、通用的代码。
-
Netty框架的目标就是让业务逻辑从网络基础应用编码中分离处理,解脱出来。
TaskQueue
任务队列中的Task有3种典型的使用场景(这部分我不是很理解):
- 用户程序自定义的普通任务
- 用户自定义定时任务
- 非当前Reactor线程调用Channel的各种方法
例如在推送系统的业务线程里面,根据用户的标识,找到对应的Channel应用,然后调用Write类方法想该用户推送消息,就会进入到这种场景。最终的Write会提交到任务队列中被异步消费。
Future-Listener机制
-
当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作。
-
常见的操作如下:
-
通过isDone方法来判断当前操作是否完成
-
通过isSuccess方法来判断已经完成的操作是否成功
-
通过getCause方法来获取已经完成的当前操作失败的原因
-
通过isCancelled方法来判断已完成的当前操作是否被取消
-
通过addlistener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果Future对象已完成,则通知指定的监听器
Bootstrap和ServerBootstrap
-
Bootstrap的意思是引导,一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中的Bootstrap类是客户端程序的引导类,ServerBootstrap是服务端启动引导类。
-
常用的方法有:
|
|
Future和ChannelFuture
- 常用方法有:
|
|
Channel
-
Netty网络通信的组件,能够用于执行网络IO操作。
-
通过Channel可获得当前网络连接的通道的状态
-
通过Channel可获得网络连接的配置参数(例如接收缓冲区大小)
-
Channel提供异步的网络IO操作(如建立连接,读写,绑定端口),异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成
-
调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以IO操作成功、失败或取消时回调通知调用方
-
支持关联IO操作与对应的处理程序
-
不同的协议、不同的阻塞类型的链接都有不同的Channel类型与之对应,常用的Channel类型有:
- NioSocketChannel,异步的客户端TCP Socket连接
- NioServerSocketChannel,异步的服务TCP Socket连接
- NioDatagramChannel,异步的UDP连接
- NioSctpChannel,异步的客户端Sctp连接
- NioSctpServerChannel,异步的Sctp服务器端连接,这些通道涵盖了UDP和TCP网络IO以及文件IO
Selector
-
Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件。
-
当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询这些注册的Channel是否有已就绪的IO事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个Channel
ChannelHandler及其实现类
-
ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序(我似乎目前没有看到转发的操作)。
-
ChannelHandler本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类
-
实现类一览:
- ChannelInboundHandler:
- ChannelOutboundHandler:
- ChannelInBoundHandlerAdapter:
- ChannelOutboundHandlerAdapter:
Netty的组件设计
-
Netty的主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。
-
ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannellnboundHandler接口(或ChannellnboundHandlerAdapter),你就可以接收入站事件和数据,这些数据随后会被你的应用程序的业务逻辑处理。当你要给连接的客户端发送响应时,也可以从ChannellnboundHandler冲刷数据。你的业务逻辑通常写在一个或者多个ChannellnboundHandler中ChannelOutboundHandler原理一样,只不过它是用来处理出站数据的
-
ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之则称为入站的
Pipeline与ChannelPipeline
- 在Netty中,每个Channel都有且仅有一个ChannelPipeline与之对应,它们的组成关系如下:
一个Channel包含了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个ChannelHandlerContext中又关联着一个ChannelHandler。
入栈事件和出站事件在一个双向链表中,入栈事件会从链表的head往后传递到最后一个入栈的handler,出站事件会从链表的tail往前专递到最前一个出站的handler,两种类型的handler互不干扰。
ChannelPipeLine的重点知识:
- ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及Channel中各个的ChannelHandler如何相互交互(我们还可以控制控制各个ChannelHandler如何交互,这是我没有想到的)
ChannelHandlerContext
-
保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象
-
即ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用.
-
常用方法:
|
|
ChannelOption
-
Netty在创建Channel实例后,一般都需要设置ChannelOption参数。
-
ChannelOption参数如下:
-
ChannelOption.SO_BACKLOG
对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
-
ChannelOption.SO_KEEPALIVE
一直保持连接活动状态
-
EventLoopGroup及其实现类NioEventLoopGroup
-
EventloopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个Eventloop同时工作,每个Eventloop维护着一个Selector实例。
-
EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个Eventloop来处理任务。在Netty服务器端编程中,我们一般都需要提供两个EventloopGroup,如:BossEventloopGroup和WorkerEventloopGroup。
-
通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理。
Unpooled类
-
Netty提供一个专门用来操作缓冲区(即Netty的数据容器)的工具类
-
常用方法:
|
|
编码解码
-
Netty提供的编码器
- StringEncoder:对字符串进行编码
- ObjectEncoder:对Java对象进行编码
-
Netty提供的解码器
- StringDecoder:对字符串进行解码
- ObjectDecoder:对Java对象进行解码
-
Netty提供的一系列实用的编解码器,都实现了ChannelInboundHandler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法都会被调用。随后,它被调用由解码器多提供的decode方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。
-
ByteToMessageDecoder直接继承于ChannelInboundHandlerAdapter。ByteToMessageDecoder会对入站数据进行缓冲,知道它准备好被处理(这个工作并不是ByteToMessageDecoder做的,而是实现decode方法是做的)
|
|
- 无论是编码器或者解码器,接受的消息类型必须与待处理的消息类型一致,否则该handler不会被执行。
ReplayingDecoder
使用这个类我们不需要调用readableBytes,参数S指定了用户状态管理的类型,其中Void代表不需要状态管理(我该如何去理解这个状态管理?)。
ReplayingDecoder有一些局限性:
-
并不是所有的ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出一个UnsupportOperationException。
-
ReplayingDecoder在某些情况下可能会鳗鱼ByteToMessageDecoder,例如网络缓慢并且消息格式复杂时,消息会被拆成多个碎片,速度变慢。
(我感觉我不需要关注这些技术,因为我极大可能性是使用Protobuf)
其他解码器
-
LineBasedFrameDecoder:这个类在Netty内部也有使用,它使用行尾控制字符(\n或者\r\n)作为分隔符来解析数据。
-
DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分隔符。
-
HttpObjectDecoder:一个HTTP数据的解码器
-
LengthFieldBasedFrameDecoder:通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息。