01 | ServerBootstrap源码分析(一)
我补充了Nio Woker的刨析,欢迎感兴趣的码友,一同学习进步。
对于netty框架,大家有一些习惯性的概念,比如线程模型、IO模型,网上有一大堆概念,每次都是看完一篇帖子就理解了,过了一会就忘记了,下面就简单讲一下吧。
为什么叫EventLoopGroup是线程模型?EventLoopGroup可以认为是netty对线程模型的一种实现,因为其对NioEventLoop的这个IO模型的一种实现,他可以管理多个NioEventLoop,内部还有实现了一个选择器EventExecutorChooser的实现。
为什么叫NioEventLoop是IO模型,其实它第一次execute执行时new了一个Thread对象,内部用这个线程一直run了,其实吧,真正可以叫IO模型是NioEventLoop内部的Selector,因为它底层有Reactor模型的实现,比如select、poll、epoll等等。
先看下Netty4 服务器端启动的demo代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverHandler);
}
});
ChannelFuture f = b.bind(PORT).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
一、Nio Boss 刨析
01 NioEventLoopGroup创建
1)NioEventLoopGroup实例创建
// boss线程初始化为1,不传参时默认为CPU核数*2
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
一些核心逻辑会在MultithreadEventExecutorGroup中,包含EventLoopGroup的创建和初始化操作等。
EventLoopGroup可以理解成一个线程池,MultithreadEventExecutorGroup有一个线程数组EventExecutor[] children属性,而传递过来的DEFAULT_EVENT_LOOP_THREADS就是数组的长度。
2)NioEventLoop实例创建
根据1中指定的线程数循环创建,每次循环初始化出一个NioEventLoop
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(executor, args);
}
}
3)NioEventLoop#Selector创建
NioEventLoop实例创建时,已经同时创建了一个selector对象。
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
rejectedExecutionHandler);
this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
// 构建两个selector对象,优化了selectedKeys的性能
final SelectorTuple selectorTuple = openSelector();
// netty包装的Selector,底层selectedKeys集合换成了数组
this.selector = selectorTuple.selector;
// 真正的Nio底层Selector,底层selectedKeys集合获取事件默认使用set方式,set底层是hash表遍历,遍历每个hash桶,hash桶遍历每个链表;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
直接调用SelectorProvider对象打开selector,打开后就可以对内部Channel(即fd)进行事件的监听与操作。
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
// 获取jdk Selector
unwrappedSelector = provider.openSelector();
...
}
4)NioEventLoop#Chooser创建
初始化出NioEventLoop的选择器,2的幂等法、轮询法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
chooser = chooserFactory.newChooser(children);
}
选择器在什么时候使用呢?
在执行注册的时候进行选择,方法ServerBootstrap#bind#doBind#initAndRegister#next()
但无论boss线程为多少,最终只会选择一个来使用,因为对于boss来说,他只注册一次。
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
从NioEventLoop的选择器中获取到一个NioEventLoop对象,还有另外一个算法,根据线程数是否是2的次幂来判断,以下是2的0次方,所以使用幂等的方式。
网上还有很多判断2的n次幂的优秀解法,比如:x & (x - 1) == 0
观察2的幂的二进制数,最高位都是1,那么判断一个数是不是2的幂,可以理解为“只有最高位是1”。
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
这里为什么要去煞费苦心的判断数组的长度是2的n次幂?
HashMap数组的长度需要是2的n次幂,因为在key值寻找数组位置的方法:(n - 1) & hash n是数组长度,这里如果数组长度是2的n次幂就可以通过位运算来提升性能。
executors.length 数组的长度默认都是CPU * 2,而一般服务器CPU核心数都是2、4、8、16,所以这里就可以配套使用了。
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
02 NioServerSocketChannel 创建注册
入口方法,NioServerSocketChannel 实例创建 -> Options设置 -> 注册
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// 2. 因为是 initAndRegister 异步执行,需要分两种情况来看,调试时也需要通过 suspend 断点类型加以区分
// 2.1 如果已经完成
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
// 3.1 立刻调用 doBind0
doBind0(regFuture, channel, localAddress, promise);
return promise;
}
// 2.2 还没有完成
else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
// 3.2 回调 doBind0
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// 处理异常...
promise.setFailure(cause);
} else {
promise.registered();
// 3. 由注册线程去执行 doBind0
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
1)NioServerSocketChannel 实例创建
其构造方法中newSocket创建了jdk的ServerSocketChannel,将新建的ServerSocketChannel缓存到AbstractNioChannel中,并将OP_ACCEPT事件赋值到变量中,稍后再进行绑定。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 创建一个 NioServerSocketChannel 实例,其构造方法中newSocket创建了jdk的ServerSocketChannel
channel = channelFactory.newChannel();
...
}
NioServerSocketChannel创建出来的时候,就已经是open状态。
private static ServerSocketChannel newSocket(SelectorProvider provider) {
return provider.openServerSocketChannel();
}
参数绑定,初始化将放到bossGroup上的channel对象相关信息:pipeline.ChannelInitializer、childGroup、childHandler等信息,并将ChannelInitializer加入到pipeline中,等待其nio-thread执行register0内的pipeline.invokeHandlerAddedIfNeeded()来触发。
void init(Channel channel) {
// 设置Options属性
setChannelOptions(channel, newOptionsArray(), logger);
// 设置额外的Attribute属性
setAttributes(channel, newAttributesArray());
// 设置Pipeline
ChannelPipeline p = channel.pipeline();
...
}
2)ServerSocketChannel#Selector关联
这里的注册是指将新建出来的NioServerSocketChannel(JDK创建出来的Channel),与bossGroup中NioEventLoop的unwrappedSelector相绑定。
NioEventLoop初始化时生成了Jdk的Selector,一个Selector在jdk中用来管理多个Channel,他是jdk开发出来对多个Channel操作与管理的一个功能。
final ChannelFuture initAndRegister() {
ChannelFuture regFuture = config().group().register(channel);
}
调用AbstractChannel中内部类AbstractUnsafe的register方法,当前线程NioEventLoop作为参数,传递过去了
public ChannelFuture register(final ChannelPromise promise) {
promise.channel().unsafe().register(this, promise);
}
绑定时关心的监听事件设置为0,表示对应Channel的FD相关事件不做监听,具体由register0中doRegister()方法实现,其调用AbstractNioChannel#doRegister方法,do相关方法就是真正干活的。
protected void doRegister() throws Exception {
// channel注册到selector,但是监听事件为0。
// 对server来说,监听事件应该是ACCEPT
// 对client来说,监听事件应该是READ
// 创建channel时,ACCEPT/READ事件以参数形式传递给父类AbstractNioChannel的readInterestOp成员变量,什么时候把ACCEPT/READ事件绑定到Selector上呢?最终会在AbstractNioChannel的doBeginRead方法中绑定到Selector上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
...
}
3)ServerSocketChannel#Handler关联
ChannelInitializer加入到pipeline中,等待其nio-thread执行register0内的pipeline.invokeHandlerAddedIfNeeded()触发。
private void register0(ChannelPromise promise) {
try {
pipeline.invokeHandlerAddedIfNeeded();
......
}
BossGroup的初始化器,自动实现ServerBootstrapAcceptor,后续worker中的Channel的连接就由它来分配。
void init(Channel channel) {
...
p.addLast(new ChannelInitializer<Channel>() {
// 这些方法是nio线程执行的
@Override
public void initChannel(final Channel ch) {
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// ServerBootstrapAcceptor的作用是在accept发生后建立连接,为什么accept事件会监听到连接事件?
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
4)ServerSocketChannel#绑定IpPort
NioServerSocketChannel注册流程结束的时候,会执行safeSetSuccess(promise)给promise对象设置返回值,触发AbstractBootstrap.doBind中regFuture.addListener#doBind0方法,进行server端口绑定。
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
if (regFuture.isDone()) {
doBind0(regFuture, channel, localAddress, promise);
} else {
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doBind0(regFuture, channel, localAddress, promise);
}
});
return promise;
}
}
将服务端的地址绑定到NioServerSocketChannel上,注意这里有一个关键参数Backlog,这个可以直接认为是全连接队列大小,默认是1024*32=32768,也就是理论上一个Channel可以管理这个多的客户端Channel。
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
触发pipeline上所有handle的fireChannelActive事件:head -> logging -> acceptor ->tail
5)ServerSocketChannel#关注ACCEPT
NioServerSocketChannel 上的 read 不是读取数据,只是为了触发 channel 的事件注册。
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
// 进入绑定事件
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
先触发head的事件:DefaultChannelPipeline.fireChannelActive->head.ChannelActive
protected void doBeginRead() throws Exception {
// readInterestOp为accept、read事件等,java.nio.channels.SelectionKey // boss:OP_ACCEPT // workor:OP_READ
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
03 NioEventLoop线程启动
AbstractChannel-AbstractUnsafe#execute#register
1)register是一个Runnable任务
boss线程第一次注册时,是main线程启动,所以由bossGroup的NioEventLoop对象来提交一个注册任务,把register当成一个任务,确保是nio线程执行注册,NioEventLoop首次执行execute时,会启动这个线程,详细见execute方法内的startThread()
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 判断当前线程和EventLoop中的线程是不是同一个线程。启动时,当前线程是main主线程,EventLoop中的线程为null,不是同一个线程
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
// 把register当成一个任务,提交给线程池eventLoop执行,确保是nio线程执行register0
// eventLoop首次execute时,会启动这个线程,详细见execute方法内的startThread();
// eventLoop.execute的另一层作用:切换线程
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
}
}
}
private void execute(Runnable task, boolean immediate) {
// 第一次进入是主线程,此变量为False
boolean inEventLoop = inEventLoop();
// 启动时,肯定不是同一个线程
if (!inEventLoop) {
// 启动一个线程执行
startThread();
}
}
2)第一次启动逻辑
使用原子类AtomicIntegerFieldUpdater保证线程安全,volatile state状态位控制当前线程的启动状态。
为什么有volatile?估计是除了io线程会提交任务,主线程也会提交任务。
为什么要有原子操作?因为startThread方法在execute方法内部,就是说每次提交一个普通任务,都需要判断当前Nio的线程是否有开启。
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
3)它是一个单线程
executor是ThreadPerTaskExecutor,在创建NioEventLoop时传的进来的,executor见ThreadPerTaskExecutor.execute
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
....
}
}
public final class ThreadPerTaskExecutor implements Executor {
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}
FastThreadLocalThread集成了Thread,所以NioEventLoop管理了这里创建的Thread
// FastThreadLocalThread集成了Thread,所以NioEventLoop管理了这里创建的Thread
public class DefaultThreadFactory implements ThreadFactory {
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
}
最终会new出一个线程赋值到NioEventLoop上的线程对象上,所以说NioEventLoop内部有一个单线程对象。
private void doStartThread() {
assert thread == null;
// executor是ThreadPerTaskExecutor,在创建NioEventLoop时传的进来的,executor见ThreadPerTaskExecutor.execute
executor.execute(new Runnable() {
@Override
public void run() {
// 设置当前线程
thread = Thread.currentThread();
...
4)thread 对象有什么用?
判断是否io线程
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
第一次启动方法的容错性校验,
线程运行过程中,允许打断
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
// 设置当前线程
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
...
04 NioEventLoop线程运行
1)只有IO事件(只有accept事件)
先检查是否有普通任务或者定时任务,都没有时执行selector.selectNow()获取io事件;
IO事件的几种场景:
如果返回CONTINUE = -2,表示应重试 IO 环路,可以继续重试获取IO事件;
如果返回BUSY_WAIT = -3,表示selector繁忙,走SELECT逻辑,因为 NIO不支持busy-wait;
如果返回SELECT = -1,表示没有获取到事件,继续判断是否有定时任务、普通任务,如果没有,将直接select(Long.MAX_VALUE)死循环获取io事件。
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT:
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
} finally {
// This update is just to help block unnecessary selector wakeups
// so use of lazySet is ok (no race condition)
nextWakeupNanos.lazySet(AWAKE);
}
// fall through
default:
}
2)IO权限ioRatio==100
检查ioRatio如果为100,先处理完io事件后要处理完全部的普通事件。
if (ioRatio == 100) {
try {
// 有io事件,处理io事件的逻辑
if (strategy > 0) {
processSelectedKeys();
}
} finally {
// 处理普通事件的逻辑,不超时直到全部
ranTasks = runAllTasks();
}
}
3)ioRatio!=100且有IO事件
先处理IO事件再处理普通事件,事件占比按比例分,事件几点以io事件处理时长来计算。
else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
// 处理io事件的逻辑
processSelectedKeys();
} finally {
// 执行io事件处理耗费的事件
final long ioTime = System.nanoTime() - ioStartTime;
// 普通事件执行的时间,如果ioRatio默认50,ioTime是8秒,普通任务也有8秒的执行时间
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
4)ioRatio!=100且没有IO事件
执行完所有的定时任务、普通任务。
else {
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
5)如果IO事件一直重复1中的CONTINUE场景
表示进入了Jdk的空轮询情况,Jdk不认为这个是一个Bug(jdk在Linux下才有此问题),所以没有优化,但Netty做了优化,空轮询512次后重建了rebuildSelector来解决此问题。
// Jdk空轮询的bug修复(jdk在Linux下才有此问题),空轮询512次后重建了rebuildSelector
else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
selectCnt = 0;
}
二、Nio Woker 刨析
01 NioEventLoopGroup创建
同Boss模式,唯一的区别是NioEventLoop的数量不一样,一般会是默认的CPU核数*2
// boss线程初始化为1,不传参时默认为CPU核数*2
EventLoopGroup bossGroup = new NioEventLoopGroup();
也需要经历以下步骤:
1)NioEventLoopGroup实例创建
2)NioEventLoop实例创建
3)NioEventLoop#Selector创建
4)NioEventLoop#Chooser创建
02 ServerBootstrapAcceptor#read()
1)客户端事件处理(Unsafe#read)
(NioMessageUnsafe#read)读取客户端连接请求信息,并执行accept 创建出NioSocketChannel
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
// doReadMessages 中执行了 accept 并创建 NioSocketChannel 作为消息放入 readBuf
// readBuf 是一个 ArrayList 用来缓存消息
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
// localRead 为 1,就一条消息,即接收一个客户端连接
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 触发 read 事件,让 pipeline 上的 handler 处理,这时是处理
// io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
2)可接入连接处理(channelRead)
对客户端Channel设置一些初始化的对象,并注册 NioSocketChannel 到 nio worker 线程,接下来的处理也移交至 nio worker 线程来处理。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这时的 msg 是 NioSocketChannel
final Channel child = (Channel) msg;
// NioSocketChannel 添加 childHandler 即初始化器
child.pipeline().addLast(childHandler);
// 设置选项
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
// 注册 NioSocketChannel 到 nio worker 线程,接下来的处理也移交至 nio worker 线程
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
1)选择woker中的NioEventLoop
Group的父类MultithreadEventLoopGroup实现选择,因为是多IO模式,选择器的模式与效果同Boss逻辑。;
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
}
2)NioSockerChannel#Selector关联
关联到eventLoop的unwrappedSelector
protected void doRegister() throws Exception {
// channel注册到selector,但是监听事件为0。
// 对server来说,监听事件应该是ACCEPT
// 对client来说,监听事件应该是READ
// 创建channel时,ACCEPT/READ事件以参数形式传递给父类AbstractNioChannel的readInterestOp成员变量,什么时候把ACCEPT/READ事件绑定到Selector上呢?最终会在AbstractNioChannel的doBeginRead方法中绑定到Selector上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
...
}
3)NioSockerChannel#Handler关联
将启动时候的初始化器直接加入进来。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
...
// NioSocketChannel 添加 childHandler 即初始化器
child.pipeline().addLast(childHandler);
...
}
如下:
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
4)NioSockerChannel#关注Read
pipeline.fireChannelActive()触发了doBeginRead()关注read事件
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 一些检查,略...
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
// 这行代码完成的事实是 nio boss -> nio worker 线程的切换
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
...
03 ServerHandler#channelRead
1)NioEventLoop线程启动
和Boss模式逻辑一样,eventLoop.execute时启动。
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 判断当前线程和EventLoop中的线程是不是同一个线程。启动时,当前线程是main主线程,EventLoop中的线程为null,不是同一个线程
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
// 把register当成一个任务,提交给线程池eventLoop执行,确保是nio线程执行register0
// eventLoop首次execute时,会启动这个线程,详细见execute方法内的startThread();
// eventLoop.execute的另一层作用:切换线程
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
}
}
}
2)NioEventLoop线程运行
和Boss模式逻辑一样。
3)NioByteUnsafe#read
再来看可读事件 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read,注意发送的数据未必能够一次读完,因此会触发多次 nio read 事件,一次事件内会触发多次 pipeline read,一次事件会触发一次 pipeline read complete
public final void read() {
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
// io.netty.allocator.type 决定 allocator 的实现
final ByteBufAllocator allocator = config.getAllocator();
// 用来分配 byteBuf,确定单次读取大小
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
// 读取
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
// 触发 read 事件,让 pipeline 上的 handler 处理,这时是处理 NioSocketChannel 上的 handler
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
}
// 是否要继续循环
while (allocHandle.continueReading());
allocHandle.readComplete();
// 触发 read complete 事件
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
是否要继续循环的判定条件。
public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
return
// 一般为 true
config.isAutoRead() &&
// respectMaybeMoreData 默认为 true
// maybeMoreDataSupplier 的逻辑是如果预期读取字节与实际读取字节相等,返回 true
(!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
// 小于最大次数,maxMessagePerRead 默认 16
totalMessages < maxMessagePerRead &&
// 实际读到了数据
totalBytesRead > 0;
}
4)正式进入业务Handler
channelRead方法就是读取客户端发送的消息的方法。
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
三、总结
01 整体上是如何运行的?
NioEventLoopGroup通过Chooser选择器选择一个NioEventLoop(Boss模式只会选择出一个),NioEventLoop线程运行时监听Selector,Selector监听NioServerSocketChannel的各种关注的事件OP_READ、OP_WRITE、OP_ACCEPT等等。
NioEventLoop是一个单线程,内部有变量Selector,它对Jdk的Selector进行管理。而一个Selector又会监听多个Channel,底层采用select、poll、epoll等算法,一个Channel就是一个FD,通常说的文件描述符。
02 ServerBootstrap为什么有option、childOption两种类似的方法?
option是Boss对应的Channel管理的参数使用,比如SO_BACKLOG参数控制TCP的连接数量,就是在此控制的。
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
@Override
void init(Channel channel) {
// 设置Options属性
setChannelOptions(channel, newOptionsArray(), logger);
...
}
}
childOption是Worker对应的NioSocketChannel客户端的参数设置。
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
...
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 这时的 msg 是 NioSocketChannel
final Channel child = (Channel) msg;
// NioSocketChannel 添加 childHandler 即初始化器
child.pipeline().addLast(childHandler);
// 设置选项
setChannelOptions(child, childOptions, logger);
...
}
}