(*文章基于Netty4.1.22版本)
在上一篇文章Netty源码分析—-服务启动之Channel初始化中,一开始给出了一个NIO的demo,然后从构造方法开始分析Netty对应的封装操作流程,而这篇文章,而这篇文章会开始分析,当初始化完成之后,Netty是如何开始接收请求的。
先看下上一篇文章NIO的demo中,是如何接收请求的(只保留run方法,其他忽略)
public class NioServer implements Runnable {
public void run() {
while (true) {
try {
selector.select(1000);
Set selectedKeys = selector.selectedKeys();
Iterator iterator = selectedKeys.iterator();
SelectionKey selectionKey = null;
while (iterator.hasNext()) {
}
} catch (IOException e) {
}
}
}
}
源码分析
Netty如何开始接收请求
那么这次需要看下Netty这部分代码在哪里,且是如何触发的。回顾一下,在Netty源码分析—-服务启动之Channel初始化中分析到,Channel会注册到Selector中,而这个Selector是在EventLoop中初始化的,那么也就是说,Selector对Channel的选择应该是在EventLoop中的,由于我们使用的是NioEventLoopGroup,所以创建的EventLoop是NioEventLoop,那么到NioEventLoop中看下,核心代码是run方法:
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
在其中我们也能看到NIO使用Selector去获取SelectionKey的影子,但这篇文章不会去详细分析run的逻辑,这个会在以后再进行分析,这里只分析启动的时候触发的流程。
从bind方法开始,有些方法会调用channel.eventLoop().execute这个方法,该方法实际调用的是SingleThreadEventExecutor的execute方法,看下这个的逻辑
public void execute(Runnable task) {
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
一开始inEventLoop为true,那么执行startThread和startTask方法,看下实现:
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
}
}
}
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();
success = true;
} finally {
}
}
});
}
使用线程池执行了一个异步的任务,首先会先设置thread = Thread.currentThread()(inEventLoop方法就是比较thread属性是否等于当前线程,由于一开始空,所以为false,还有其他的情况就是当前线程和EventLoop的线程是否同一个),然后调用了run方法,那么这时候就是执行的NioEventLoop的run方法了。
再看下addTask的实现
protected void addTask(Runnable task) {
if (!offerTask(task)) {
reject(task);
}
}
final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task);
}
addTask就是将任务放到队列中,顺便的说一下在NioEventLoop的方法里,会分开两部分时间,有一部分时间执行io任务,就是处理连接,read,write等事件,一部分就是处理自定义的任务,就是通过EventLoop的execute方法加入的任务,也就是队列里的任务。
整个流程图如下:
注:startTread方法不一定是在注册的时候调用的,因为启动该线程的条件是添加任务的时候还未启动,刚好这里注册的时候第一次加入任务,所以在这里启动,如果其他版本在这之前还加入了任务,那么到注册的时候就是直接加入任务,而不需要启动线程
其他初始化
从initAndRegister开始看起,因为上篇文章有些细节没有看完
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
}
return regFuture;
}
这个init上篇文章没有分析到,这个实现在ServerBootstrap中
void init(Channel channel) throws Exception {
final Map, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
final Map, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey