ServerBootStrap 服务引导器,负责组织各个组件完成服务器的启动,在底层被封装成ServerBootstrapConfig(作为构造参数传入)
NioEventLoopGroup 事件循环组,维护NioEventLoop,每个事件循环对象实际上就是一个selector+处理任务/定时任务
的单例线程池实现多路复用,涉及到Netty的Reactor模型
NioServrSocketChannel nio服务器通道,由他真正进行服务器的启动,构造方法中ServrSocketChannel.open() 创建服务器,并注册到selector
上将NioServerSocketChannel作为ServerSocketChannel的附件,方便后续可以通过selectionKey获取到对应附件(attathment)
tips:channel(NioServerSocketChannel.class)会被放入ChannelFactory中,通过工厂的newChannel进行实例化
childHandler 指定通道的处理器,通常为ChannelInitializer<NioSocketChannel>通道初始化器
ChannelFuture regFuture = config().group().register(channel);
tips:该操作是异步
通过ServerBootStrap的boss事件循环组执行NioServerSocketChannel的注册,首先获取事件循环对象执行register方法,将channel封装
成new DefaultChannelPromise(channel, this)默认通道Promise,用于在多个线程间交换结果,通过通道的NioUnsafe对象进行注册
promise.channel().unsafe().register(this, promise);
AbstractChannel.this.eventLoop = eventLoop; 将事件循环对象复制给当前通道方便后续进行异步处理的判断(判断下个操作执行线程与当前
线程是否是同一个线程)
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
方法:eventLoop.inEventLoop() 判断当前线程与调用者eventLoop事件循环对象线程是否是同一线程,如果是直接在当前线程执行操作,否则如下
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
通过事件循环对象的execute方法,启动一个任务执行具体任务
事件循环对象 NioEventLoop 父类 SingleThreadEventLoop 继承了 SingleThreadEventExecutor 单例线程池(维护任务队列和定时任务队列)
实现EventLoop 他又继承 EventLoopGroup
在AbstractChannel中register0方法doRegister();
tips:关于包装和未包装,是Netty对象Selector内部SelectionKey集合的数据结构
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
获取java中ServerSocketChannel注册到事件循环对象未包装的Selector上,0表示不关注通道上任何事件,并将this(实际指NioServerSocketChannel)
作为附件进行存储,方便后续通过SelectionKey进行获取,最终完成通道到Selector的注册
neverRegistered = false;
registered = true;
// 如有必要调用HandlerAdded方法
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//完成通道处理器的注册,包括内置head第一位,tail最后一位
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
beginRead(); 开始读取
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
通过获取通道对应的SelectionKey,绑定感兴趣事件,在原有基础上加上读事件
上述是初始化逻辑
运行,处理事件逻辑NioEventLoop的run方法内部是一个死循环
selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()) 计算策略查看当前事件循环对象有无任务
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
有任务selector.selectNow()返回当前selector对应的事件key(selectionKey)否则切换选择策略selectStrategy未select(默认也是select)
select(wakenUp.getAndSet(false));方法参数getAndSet传递新值返回旧值,方法内部首先获取当前事件循环对象的Selector
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
进行selector.select(long timeout)阻塞时间的计算
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
tips:wakenup为true代表selector.select(long timeout)方法本次将不再阻塞
如果wakenup为true有任务提交,则该任务没有机会调用Selectorwakeup,所以需要在执行操作之前再次检查任务队列,避免任务被一直挂起
直到选择操作超时(摘自官网)
原因: select(wakenUp.getAndSet(false));一个旧值为true的情况,新值已经是false,而wakeup方法的调用只是会让下次的select方法不再阻塞
所以 int selectedKeys = selector.select(timeoutMillis);该操作将会一直阻塞直到超时
selectCnt = 1 变量的作用是用于标记
int selectedKeys = selector.select(timeoutMillis) 没有事件发生时将阻塞指定时长
后续还有针对于线程中断,任务,定时任务,wakenup新值,旧值判断 ,SelectionKeys(当前发生的事件的Channel数量)
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// 阻塞时间超时,将标注变量进行重置
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
// 判断成立出现epoll空轮询,解决方案重构一个一模一样的Selector进行赋值,并重置标识位selectCnt
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
// 重置当前时间
currentTimeNanos = time;
}
上述epoll空轮询的办法中jdk10已经完美解决,详见事件循环对象static{}静态块中jdk10的处理
跳出循环后针对于定时/普通 任务与 io事件进行的分开处理
final int ioRatio = this.ioRatio;
获取当前io事件的执行几率如果ioRatio为100时将会优先执行所有的io事件(read,write,accept,connect..)最后再执行所有的任务
否则也是先执行所有io事件
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
获取io事件的执行时间*任务执行比率/io事件执行比率
processSelectedKeysOptimized处理使用的就是Netty针对于SelectionKey集合数据结构的优化,默认NIO中的数据结构是Set集合也就是hash表只是value指向同一个内部值
真正io事件处理逻辑processSelectedKeysOptimized中遍历所有的selectedKeys集合拿到SelectionKey对象获取对应的attachement附件(也就是
我们初始化注册时的NioServerSocketChannel/NioSocketChannel)对附件类型进行判断,进行对应转换并调用方法processSelectedKey(k, (AbstractNioChannel) a) 进行处理
NioEventLoop类中processSelectedKey方法处理逻辑
1.获取NioServerSocketChannel/NioSocketChannel的NioUnsafe对象
2.校验SelectionKey事件key是否有效
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// 获取当前SelectionKey感兴趣的参数(就是Selector会关注Channel的哪些io事件)
int ops = k.interestOps();
// 将连接事件移除,避免Selector.select() 一直无法阻塞,因为对应事件处理完以后Selector并不会将事件从Set<SelectionKey>/SelectionKey[]中移除
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
// 在实际读取/写出前,先调用finishConnect()方法,否则Nio JDK通道可能会抛出NotYetConnectedException异常
// 完成客户端和服务端连接,如果连接断开则抛出异常
unsafe.finishConnect();
}
读写事件处理,注入出入站处理器的概念,出站处理器中,两种处理情况一种从当前处理器往前写出,一种从tail内置处理器开始写出,包括tail和header也会对资源ByteBuf进行release释放,
资源没改变类型等的情况下
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
读事件和Accept(接受客户端连接事件)以及首次没有关注任何事件的处理
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
下面详解 【 读事件和Accept(接受客户端连接事件)以及首次没有关注任何事件的处理】
final ChannelConfig config = config(); 获取通道对应的配置类 NioServerSocketChannel/NioSocketChannel,配置中包含了一些核心参数的调优(例如backlog全连接队列,ReceiveBufferSize,SendBufferSize接收
缓冲区与发送缓存区默认加载参数)
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
上述代码都涉及一些核心参数的加载,可以翻阅源码,查看如何定制,接收缓冲区和发送缓冲区一般是不需要更改的,他能够根据网络设备进行自适应设置
final ChannelPipeline pipeline = pipeline(); 获取当前通道流水线(pipeline)
//分配ByteBuf大小
byteBuf = allocHandle.allocate(allocator);
//将通道内数据写入ByteBuf
allocHandle.lastBytesRead(doReadBytes(byteBuf));
do{}while()中循环读取,只要allocHandle.lastBytesRead() <= 0就释放ByteBuf内存,终止循环
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
增加当前循环读,已读取的消息数
allocHandle.incMessagesRead(1);
触发通道内处理器的读事件,并将ByteBuf作为参数进行传递
pipeline.fireChannelRead(byteBuf);
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
// 在读取时关闭,内部对ChannelConfig参数进行是否允许isAllowHalfClosure(半封闭)判断
// pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);并通过通道流水线对象pipeline触发通道内所有处理器Hanler的用户事件触发
closeOnRead(pipeline);
}
如果时Accept事件则进入AbstractNioMessageChannel中的read方法
doReadMessages方法中
SocketChannel ch = SocketUtils.accept(javaChannel()); 建立与客户端连接并返回与对应客户端通信的SocketChannel
pipeline.fireChannelRead(readBuf.get(i));中通过DefaultChannelPipeline中内部类HeadContext的read方法完成初次io事件触发时的interestOps事件的绑定,注意accept和read的方法一样
但是实际断点发现携带的参数值不一样,需要注意
Netty中用到的责任链设计模式,通过事件循环对象的inEventLoop方法判断当前线程与事件循环对象线程是否一致来进行分别处理,通过AbstractChannelHandlerContext通道处理上下文来获取
下一个处理器(In/Out Bound)上下文,并获取该上下文中的事件循环对象进行线程判断进而直接处理或开启一个任务进行处理
连接建立代码: SocketChannel ch = SocketUtils.accept(javaChannel());
Channelnitializer通道初始化器initChannel方法调用时机,在首次连接建立成功,执行完注册逻辑 selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
也就是将SocketChannel注册到Selector上并将NioSocketChannel作为附件
pipeline.invokeHandlerAddedIfNeeded(); 通过通道流水线对象,如有必要回调通道内所有的HandlerAdded方法,ChannelInitializer通道初始化器中handlerAdded
if (initChannel(ctx)) {
// We are done with init the Channel, removing the initializer now.
removeState(ctx);
}
该判断方法中 内部 调用 initChannel((C) ctx.channel()); 执行连接建立成功时,通道的处理器添加逻辑