Netty
先说下java的三种IO模型
BIO
BIO 同步阻塞, 一客户端对应一个处理线程, accept和read方法都是阻塞操作,如果没有连接请求,accept方法阻塞;如果无数据可读取,read方法阻塞

NIO
NIO是同步非阻塞模型, 一个线程循环遍历,接入连接,然后分发给下面的线程


NIO三大组件
- Buffer:用于存储数据,底层基于数组实现,针对8种基本类型提供了对应的缓冲区类。
- Channel:用于进行数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从Buffer写到Channel中。
- Selector:选择器,当向一个Selector中注册Channel后,Selector 内部的机制就可以自动不断地查询(Select)这些注册的Channel是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个Channel,也可以说管理多个网络连接,因此,Selector也被称为多路复用器。当某个Channel上面发生了读或者写事件,这个Channel就处于就绪状态,会被Selector监听到,然后通过SelectionKeys可以获取就绪Channel的集合,进行后续的I/O操作

Epoll是Linux下多路复用IO接口select/poll的增强版本, 获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了
select和epoll的区别
当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:
- 使用多进程或者多线程,但是这种方法会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销(Apache服务器是用的子进程的方式,优点可以隔离用户);
- 用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否可读,这种形式的循环为轮询(polling),这种方法比较浪费CPU时间,因为大多数时间是不可读,但是仍花费时间不断反复执行read系统调用;
- 异步I/O,当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符时不适用;
- 一种较好的方式为I/O多路复用,先构造一张有关描述符(FileDescriptor)的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。
它们的区别主要有三点:
- select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目;
- epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对”活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个”伪”AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂);
- 使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
AIO
参考代码
AIO是异步非阻塞模型,一般用于连接数较多且连接时间较长的应用,在读写事件完成后由回调服务去通知程序启动线程进行处理。与NIO不同,当进行读写操作时,只需直接调用read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
Netty

Channel - Socket
EventLoop - 控制流 多线程处理
ChannelFuture
- Bootstrap or ServerBootstrap
- EventLoop
- EventLoopGroup
- ChannelPipeline (提供了 ChannelHandler 链的容器)
- Channel
- Future or ChannelFuture
- ChannelInitializer
- ChannelHandler
Netty 包含两组线程池
BossGroup(接收客户端发来的链接) 和 WorkGroup(完成TCP三次握手等操作)
NioEventLoopGroup
线程池调度服务, 继承自ScheduledExecutorService, 包含了多个NioEventLoop,管理NioEventLoop的生命周期,
每个NioEventLoop 包含一个NIO selector 一个队列 一个线程, 线程用来轮询, 当注册一个Channel后, Netty将此Channel绑定到EventLoop上, io操作不需要同步,因为一直在一个线程上
Boss NioEventLoop
- 处理Accept事件,与client建立连接,生成NioSocketChannel
- 将NioSocketChannel注册到worker NioEventLoop上的selector
- 处理任务队列的任务, 即runAllTasks
Worker NioEventLoop
- 轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。
- 处理read和write事件,在对应NioSocketChannel处理业务。
- unAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理,这样不影响数据在pipeline中的流动处理。
ChannelPipeline
提供了 ChannelHandler 链的容器,消息在ChannelPipline中流动和传递, 持有I/O事件拦截器ChannelHandler的双向链表
ChannelPipeline是一系列的ChannelHandler实例,每当一个新的Channel被创建了,都会建立一个新的ChannelPipeline并绑定到该Channel上,这个关联是永久性的

当有新的ChannelHandler被添加时,则将其封装为ChannelHandlerContext对象,然后插入到链表中。
ChannelHandler
它充当了所有处理入站和出站数据的应用程序逻辑的容器。
@Sharable注解共享一个ChannelHandler在 多个实例中, 经常用来统计
public interface ChannelInboundHandler extends ChannelHandler {
//当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
//当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
//当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
void channelActive(ChannelHandlerContext ctx) throws Exception;
//当 Channel 离开活动状态并且不再连接它的远程节点时被调用
void channelInactive(ChannelHandlerContext ctx) throws Exception;
当从 Channel 读取数据时被调用
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
//当 Channel上的一个读操作完成时被调用
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
ChannelHandlerContext
负责在链上传播责任处理器接口的事件。 它有两个重要的方法,查找Inbound类型和Outbound类型的处理器。
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
//查找下一个Inbound类型的处理器,左 > 右
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.next;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
return ctx;
}
//查找下一个Outbound类型的处理器,右 > 左
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.prev;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
return ctx;
}
}
ctx.fireChannelRead(msg); 责任链一直调用下去
ByteBuf
ChannelPipeline 包含ChannelHandlerContext(ChannelHandler) 链表
状态
初始化
EventLoopGroup
多个EventLoop(newTaskQueue selector (selectorKeys))
private ChannelFuture doResolveAndConnect(SocketAddress remoteAddress, final SocketAddress localAddress) {
// 初始化并注册
final ChannelFuture regFuture = initAndRegister();
if (regFuture.cause() != null) {
return regFuture;
}
final Channel channel = regFuture.channel();
final EventLoop eventLoop = channel.eventLoop();
final NameResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
return doConnect(remoteAddress, localAddress, regFuture, channel.newPromise());
}
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
// Failed to resolve immediately
channel.close();
return channel.newFailedFuture(resolveFailureCause);
}
if (resolveFuture.isDone()) {
// Succeeded to resolve immediately; cached? (or did a blocking lookup)
return doConnect(resolveFuture.getNow(), localAddress, regFuture, channel.newPromise());
}
// Wait until the name resolution is finished.
final ChannelPromise connectPromise = channel.newPromise();
resolveFuture.addListener(new FutureListener<SocketAddress>() {
@Override
public void operationComplete(Future<SocketAddress> future) throws Exception {
if (future.cause() != null) {
channel.close();
connectPromise.setFailure(future.cause());
} else {
doConnect(future.getNow(), localAddress, regFuture, connectPromise);
}
}
});
return connectPromise;
}

浙公网安备 33010602011771号