源码

启动流程

// Netty 使用 NioEventLoopGroup(NIO Boss 线程)来封装线程和 Selector
Selector selector = Selector.open(); 

// 创建 NioServerSocketChannel,同时会初始化它关联的 handler,以及为原生 ServerSocketChannel 存储 config
NioServerSocketChannel attachment = new NioServerSocketChannel();

// 创建 NioServerSocketChannel 时,创建 Java 原生 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
serverSocketChannel.configureBlocking(false);

// 启动 NIO Boss 线程执行接下来的操作

//注册(仅关联 selector 和 NioServerSocketChannel),未关注事件
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, attachment);

// head -> 初始化器 -> ServerBootstrapAcceptor -> tail,初始化器是一次性的,只为添加 acceptor

// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));

// 触发 channel active 事件,在 head 中关注 op_accept 事件
selectionKey.interestOps(SelectionKey.OP_ACCEPT);

 

io.netty.bootstrap.AbstractBootstrap.doBind

1、服务器启动入口

2、真正完成初始化、注册、绑定的方法

3、dobind 方法在主线程中执行

4、doBind 的两个重要方法

(1)initAndRegister:负责创建 NioServerSocketChannel、ServerSocketChannel(主线程中完成),注册 ServerSocketChannel(NIO 线程中完成)

(2)doBind0:负责连接的创建工作

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 负责NioServerSocketChannel和ServerSocketChannel的创建
    // ServerSocketChannel的注册工作
    // init由main线程完成,regisetr由NIO线程完成
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    // 因为register操作是异步的
    // 所以要判断主线程执行到这里时,register操作是否已经执行完毕
    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();

        // 由NIO线程异步执行doBind0绑定操作
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        // 如果register操作还没执行完,就会到这个分支中来
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);

        // 添加监听器,NIO线程异步进行doBind0操作
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                    // IllegalStateException once we try to access the EventLoop of the Channel.
                    promise.setFailure(cause);
                } else {
                    // Registration was successful, so set the correct executor to use.
                    // See https://github.com/netty/netty/issues/2586
                    promise.registered();

                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

 

initAndRegister

final ChannelFuture initAndRegister() {
    Channel channel = null;
    //init操作
    try {
        channel = channelFactory.newChannel();
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            // channel can be null if newChannel crashed (eg SocketException("too many open files"))
            channel.unsafe().closeForcibly();
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
        return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    //register操作
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }

    // If we are here and the promise is not failed, it's one of the following cases:
    // 1) If we attempted registration from the event loop, the registration has been completed at this point.
    //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
    // 2) If we attempted registration from the other thread, the registration request has been successfully
    //    added to the event loop's task queue for later execution.
    //    i.e. It's safe to attempt bind() or connect() now:
    //         because bind() or connect() will be executed *after* the scheduled registration task is executed
    //         because register(), bind(), and connect() are all bound to the same thread.

    return regFuture;
}

1、init 操作

init主要完成以下三个操作
1、创建NioServerSocketChannel
2、通过NioServerSocketChannel构造器,创建ServerSocketChannel
3、由initChannel方法向NioServerSocketChannel中添加两个handler,添加操作在register之后被执行
(1)一个handler负责设置配置
(2)一个handler负责发生Accepet事件后建立连接
Channel channel = null;
try {
    // 通过反射初始化NioServerSocketChannel
    channel = channelFactory.newChannel();
    init(channel);
}

(1)newChannel()

@Override
public T newChannel() {
    try {
        // 通过反射调用NioServerSocketChannel的构造方法
        // 创建NioServerSocketChannel对象
        return constructor.newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
    }
}

(2)constructor.newInstance():NioServerSocketChannel()

public NioServerSocketChannel() {
    // 创建ServerSocketChannel实例
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

(3)newSocket(DEFAULT_SELECTOR_PROVIDER)

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        // 原生ServerSocketChannel.open方法中:SelectorProvider.provider().openServerSocketChannel()
        // 所以此处相当于ServerSocketChannel.open()
        // 创建了ServerSocketChannel实例
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a server socket.", e);
    }
}

(4)init(Channel channel)

@Override
void init(Channel channel) {
    ……
    // NioSocketChannl的Pipeline    
    ChannelPipeline p = channel.pipeline();
    ……
    // 向Pipeline中添加一个handler,该handler等待被调用
    //ChannelInitializer与其他Handler区别:只执行一次initChannel方法
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        // register之后才调用该方法
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            
            // 创建handler并加入到pipeline中
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 添加新的handler,在发生Accept事件后建立连接
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

2、register 操作

Register主要完成以下三个操作
1、完成主线程到NIO线程切换
(1)通过eventLoop.inEventLoop()进行线程判断,判断当前线程是否为NIO线程
(2)切换方式:eventLoop执行register操作
(3)register操作在NIO线程中完成
2、调用doRegister方法
(1)将ServerSocketChannel注册到EventLoop的Selector中
(2)此时还未关注事件
(3)添加NioServerSocketChannel附件
3、通过invokeHandlerAddedIfNeeded调用init中的initChannel方法
(1)initChannel方法主要创建两个handler
(2)一个handler负责设置配置
(3)一个handler负责发生Accept事件后建立连接
ChannelFuture regFuture = config().group().register(channel);

(1)register(channel)

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ……
    // 获取EventLoop
    AbstractChannel.this.eventLoop = eventLoop;

    // 此处完成了由 主线程 到 NIO线程 的切换
    // eventLoop.inEventLoop():判断当前线程是否为NIO线程
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            // 首次执行时,启动EventLoop关联的NIO线程,向NIO线程中添加任务
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    // 该方法中会执行doRegister
                    // 执行真正的注册操作
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ……
        }
    }
}

(2)register0(promise)

private void register0(ChannelPromise promise) {
    try {
        ……
        // 执行真正的注册操作
        doRegister();
        neverRegistered = false;
        registered = true;

        // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
        // user may already fire events through the pipeline in the ChannelFutureListener.
        
        // 调用init中的initChannel方法
        pipeline.invokeHandlerAddedIfNeeded();
        //向Promise中设置执行成功的结果
        safeSetSuccess(promise);
        ……
    } catch (Throwable t) {
        ……
    }
}

(3)doRegister()

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // javaChannel():获取原生ServerSocketChannel
            // eventLoop().unwrappedSelector():获取eventLoop中的Selector
            // this为NIOServerSocketChannel,作为附件
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            ……
        }
    }
}

(4)回调 initChannel

@Override
public void initChannel(final Channel ch) {
    final ChannelPipeline pipeline = ch.pipeline();

    // 创建handler并加入到pipeline中
    ChannelHandler handler = config.handler();
    if (handler != null) {
        pipeline.addLast(handler);
    }

    ch.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            // 添加新的handler,在发生Accept事件后建立连接
            pipeline.addLast(new ServerBootstrapAcceptor(
                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

 

doBind0

1、绑定端口

(1)在 doRegister 和 invokeHandlerAddedIfNeeded 操作完成后,调用 safeSetSuccess(promise) 方法,向 Promise 中设置执行成功的结果

(2)此时 doBind 方法中由 initAndRegister 返回的 ChannelFuture 对象 regFuture,会由 NIO 线程异步执行 doBind0 绑定操作

// initAndRegister为异步方法,会返回ChannelFuture对象
final ChannelFuture regFuture = initAndRegister();
……
regFuture.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        Throwable cause = future.cause();
        if (cause != null) {
            // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
            // IllegalStateException once we try to access the EventLoop of the Channel.
            promise.setFailure(cause);
        } else {
            // Registration was successful, so set the correct executor to use.
            // See https://github.com/netty/netty/issues/2586
            promise.registered();
            // 如果没有异常,则执行绑定操作
            doBind0(regFuture, channel, localAddress, promise);
        }
    }
});

(3)NioServerSocketChannel 的 doBind 方法

@SuppressJava6Requirement(reason = "Usage guarded by java version check")
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        //doBind0最底层调用原生ServerSocketChannel的bind方法,绑定端口
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

2、关注事件

(1)在绑定端口操作完成后,会判断各种所有初始化操作是否已经完成,若完成,则会添加 ServerSocketChannel 感兴趣的事件

if (!wasActive && isActive()) {
    invokeLater(new Runnable() {
        @Override
        public void run() {
            //触发channel上pipeline所有handler的active事件
            pipeline.fireChannelActive();
        }
    });
}

(2)最终在 AbstractNioChannel 的 doBeginRead 方法中,为 ServerSocketChannel 添加 Accept 事件

@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }
    readPending = true;
    //此处设置interestOps时使用的方法,避免覆盖关注的其他事件
    //首先获取Channel所有感兴趣的事件
    final int interestOps = selectionKey.interestOps();
    // 如果ServerSocketChannel没有关注Accept事件
    if ((interestOps & readInterestOp) == 0) {
        // 则让其以添加的方式,关注Accepet事件
        // readInterestOp 取值是 16
        // 在 NioServerSocketChannel 创建时初始化
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

 

NioEventLoop

1、重要组成

(1)Selector

public final class NioEventLoop extends SingleThreadEventLoop {
    ……
    // selector中的selectedKeys是基于数组的
    // unwrappedSelector中的selectedKeys是基于HashSet的    
    private Selector selector;
    private Selector unwrappedSelector;
    private SelectedSelectionKeySet selectedKeys;
    ……
}

(2)Thread

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    ……
    // 一个NioEventLoop对应一个线程
    private volatile Thread thread;
    // 执行器,单线程线程池,与thread为同一线程,但有更多功能
    private final Executor executor;
    ……
}

(3)TaskQueue

public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
    ……
    // 任务队列
    private final Queue<Runnable> taskQueue;
    ……
}
public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
    ……
    //定时任务队列
    PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
    ……
}

2、创建 Selector

(1)Selector 在 NioEventLoop 的构造方法中被创建

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler, EventLoopTaskQueueFactory queueFactory) {
    ……
    // 初始化selector,初始化过程在openSelector中
    final SelectorTuple selectorTuple = openSelector();
    this.selector = selectorTuple.selector;
    this.unwrappedSelector = selectorTuple.unwrappedSelector;
}

(2)调用 openSelector() 方法, 该方法返回一个 SelectorTuple 对象,该方法是创建 Selector 的核心方法

private SelectorTuple openSelector() {
    final Selector unwrappedSelector;
    try {
        // 等价于Selector.open()
        // 创建unwrappedSelector对象
        unwrappedSelector = provider.openSelector();
    } catch (IOException e) {
        throw new ChannelException("failed to open a new selector", e);
    }
    ……
    // 获得基于数组的selectedKeySet实现,由Netty提供
    final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();

    Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            try {
                // 通过反射拿获取nwrappedSelector中的selectedKeys、publicSelectedKeys属性
                Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
                ……
                // 暴破反射,修改私有属性
                Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
                if (cause != null) {
                    return cause;
                }
                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
                if (cause != null) {
                    return cause;
                }

                // unwrappedSelector的原生基于HashSet的SelectionKeySet,替换为基于数组的selectedKeySet
                selectedKeysField.set(unwrappedSelector, selectedKeySet);
                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                return null;
            } catch (NoSuchFieldException e) {
                return e;
            } catch (IllegalAccessException e) {
                return e;
            }
        }
    });

    //private SelectedSelectionKeySet selectedKeys;
    selectedKeys = selectedKeySet;
    
    //new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)
    //通过Selector的构造方法获得Selector,其SelectionKeySet基于数组实现
    return new SelectorTuple(unwrappedSelector,
                             new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}

(3)SelectedSelectionKeySet 无参构造器,创建基于数组的 SelectionKey

SelectedSelectionKeySet() {
    keys = new SelectionKey[1024];
}

(4)NioEventLoop 内部类:SelectorTuple

private static final class SelectorTuple {
    final Selector unwrappedSelector;
    final Selector selector;

    SelectorTuple(Selector unwrappedSelector) {
        this.unwrappedSelector = unwrappedSelector;
        this.selector = unwrappedSelector;
    }

    //将该Selector的值赋给SelectorTuple类中的selector与unwrappedSelector
    //通过SelectorTuple的构造方法,获得拥有两种Selector的SelectorTuple对象,并返回给NioEventLoop
    SelectorTuple(Selector unwrappedSelector, Selector selector) {
        this.unwrappedSelector = unwrappedSelector;
        this.selector = selector;
    }
}

 

两个 Selector

1、NioEventLoop 中有 selector 和 unwrappedSelector

2、区别:SelectedKeys 数据结构不同

(1)selector 中的 SelectedKeys 基于数组

(2)unwrappedSelector 中的 SelectedKeys 基于 HashSet

3、目的:数组的遍历效率要高于HashSet

 

NIO 线程启动时机

1、NioEventLoop 中的线程,在首次执行任务时,才会被创建,且只会被创建一次

2、SingleThreadEventExecutor.execute

@Override
public void execute(Runnable task) {
    execute0(task);
}
private void execute0(@Schedule Runnable task) {
    // 检测传入的任务是否为空,为空会抛出NullPointerException
    ObjectUtil.checkNotNull(task, "task");
    // 执行任务
    // 此处判断了任务是否为懒加载任务,wakesUpForTask的返回值只会为true
    execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
private void execute(Runnable task, boolean immediate) {
    // 判断当前线程是否为NIO线程
    // 判断方法为 return thread == this.thread;
    // this.thread即为NIO线程,首次执行任务时,其为null
    // thread为Thread.currentThread()
    boolean inEventLoop = inEventLoop();
    
    // 向任务队列taskQueue中添加任务
    addTask(task);
    
    // 当前线程不是NIO线程,则进入if语句
    if (!inEventLoop) {
        // 启动NIO线程的核心方法
        startThread();
        ……
    }
	
    // 有任务需要被执行时,唤醒阻塞的NIO线程
    if (!addTaskWakesUp && immediate) {
        wakeup(inEventLoop);
    }
}
private void startThread() {
    // 查看NIO线程状态是否为未启动
    // 该if代码块只会执行一次
    // state一开始的值就是ST_NOT_STARTED
    // private volatile int state = ST_NOT_STARTED;
    if (state == ST_NOT_STARTED) {
        // 通过原子属性更新器,将状态更新为启动(ST_STARTED)
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            boolean success = false;
            try {
                //启动NIO线程,并执行任务
                doStartThread();
                success = true;
            } finally {
                if (!success) {
                    STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                }
            }
        }
    }
}
private void doStartThread() {
    assert thread == null;
    // 创建NIO线程并执行任务
    executor.execute(new Runnable() {
        @Override
        public void run() {
            // thread即为NIO线程,由当前线程赋值
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }

            boolean success = false;
            updateLastExecutionTime();
            try {
                //执行传入的任务
                //该run方法是NioEvnetLoop的run方法
                SingleThreadEventExecutor.this.run();
                success = true;
            } 
            ……
    });
}

 

进入 SELECT 分支阻塞

1、NioEvnetLoop 需要 I/O 事件、普通任务、定时任务

2、任务在 run 方法的 for 循环中

@Override
protected void run() {
    int selectCnt = 0;
    // 死循环,不断地从任务队列中获取各种任务来执行
    for (;;) {
        // 执行各种任务
        ……
    }
}

3、该循环不会空转,执行到某些代码时,会被阻塞

4、run 方法的 switch 语句有多条分支

//具体执行分支的代码由 strategy 变量控制
int strategy;
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
	……
}

5、strategy 值由 calculateStrategy 方法确定

@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
    return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}

6、根据 hasTask 变量判断任务队列中是否有任务

private final IntSupplier selectNowSupplier = new IntSupplier() {
    //若有任务,则通过selectSupplier获得strategy的值
    public int get() throws Exception {
        return NioEventLoop.this.selectNow();
    }
};
     
int selectNow() throws IOException {
    // selectSupplier.get() 底层是 selector.selectNow();
    return this.selector.selectNow();
}

7、若没有任务,就会进入 SELECT 分支,使 NIO 线程阻塞

case SelectStrategy.SELECT:
	long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
	if (curDeadlineNanos == -1L) {
        curDeadlineNanos = NONE; // nothing on the calendar
    }
	nextWakeupNanos.set(curDeadlineNanos);
	try {
    	if (!hasTasks()) {
            // 执行select方法
            strategy = select(curDeadlineNanos);
        }
    }

8、执行 NioEvnetLoop 的 select 方法,该方法内部会根据情况,执行 selector 有参 / 无参 select 方法

private int select(long deadlineNanos) throws IOException {
    // 如果没有指定阻塞事件,就调用select()
    if (deadlineNanos == NONE) {
        return selector.select();
    }
    // 否则调用select(timeoutMillis),指定时间内未发生事件就停止阻塞
    //超时时间(ms)=(截止时间(ns)+ 995000(ns))/ 1000000 = 截止时间(ms)+ 0.995(ms)
    //若截止时间在 5 微秒以内,则超时时间为0
    long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
    return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}

 

唤醒

1、select 方法阻塞线程,当没有 I/O 事件,但有其他任务需要执行时,需要唤醒线程

2、通过 execute 最后的 if 代码块完成唤醒

// 有任务需要被执行时,唤醒阻塞的NIO线程
if (!addTaskWakesUp && immediate) {
    wakeup(inEventLoop);
}

3、NioEventLoop.wakeup 唤醒被 select 方法阻塞的 NIO 线程

@Override
protected void wakeup(boolean inEventLoop) {
    //!inEventLoop:判断提交任务的是否为NIO线程
    //若是其他线程,才能唤醒 NIO 线程
    //若是 NIO 线程本身,则不能唤醒
    //只有当其他线程给当前NIO线程提交任务时(如:执行execute),才会被唤醒

    //nextWakeupNanos.getAndSet(AWAKE) != AWAKE
    //通过AtomicLong进行更新,保证有多个线程同时提交任务时,只有一个线程能够唤醒NIO线程

    if (!inEventLoop && nextWakeupNanos.getAndSet(AWAKE) != AWAKE) {
        // 唤醒被selector.select方法阻塞的NIO线程
        selector.wakeup();
    }
}

 

Java NIO 在 Linux 系统下 epoll 空轮询问题

1、在 NioEventLoop 中,run 方法存在一个死循环,需要通过 select 方法阻塞线程

(1)但 select 方法存在 BUG,可能无法阻塞线程,导致循环一直执行,使得 CPU 负载升高

(2)Netty 中通过 selectCnt 变量,检测 select 方法是否发生空轮询 BUG

(3)若发生空轮询 BUG,那么 selectCnt 值的增长是十分迅速

@Override
protected void run() {
    //Netty中通过selectCnt变量来检测select方法是否发生空轮询BUG
    int selectCnt = 0;
    for(;;){
        ……
        // 可能发生空轮询,无法阻塞NIO线程
        strategy = select(curDeadlineNanos);  
        ……
        //若发生空轮询BUG,那么selectCnt的值增长十分迅速
        selectCnt++;
        ……
        if(ranTasks || strategy > 0) {
            ……
        } else if (unexpectedSelectorWakeup(selectCnt) ){
            // 通过unexpectedSelectorWakeup方法中的rebuildSelector重建selector
            // 并将selectCnt重置为0
            selectCnt = 0;
        }
    }
}

2、通过 unexpectedSelectorWakeup 方法中的 rebuildSelector 重建 selector,并将 selectCnt 重置为 0

(1)selectCnt 大于等于 SELECTOR_AUTO_REBUILD_THRESHOLD(默认 512)时,Netty 则判断其出现空轮询 BUG,进行如下处理

(2)通过 rebuildSelector 方法重建 selector,将原 selector 配置信息传给新 selector,再用新selector 覆盖旧 selector,同时将 selectCnt 值设置为 0

if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
    // The selector returned prematurely many times in a row.
    // Rebuild the selector to work around the problem.
    logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",selectCnt, selector);
    // 重建selector,将原selector的配置信息传给新selector
    // 再用新selector覆盖旧selector
    rebuildSelector();
    return true;
}

 

ioRatio

1、NioEventLoop 可以处理 I/O 事件、普通任务、定时任务,不同操作所耗费时间不同

2、通过 ioRatio 控制 NioEventLoop 处理 I/O 事件花费时间,占执行所有操作的总时间的比例

3、NioEventLoop.run()

// 处理IO事件时间比例,默认为50%
final int ioRatio = this.ioRatio;
……
// 判断IO事件时间比例设置是否为100%
if (ioRatio == 100) {
    try {
        // 若是,判断是否需要处理IO事件
        if (strategy > 0) {
            // 先处理IO事件
            processSelectedKeys();
        }
    } finally {
        //若否(或IO事件已经处理完毕),执行所有的普通任务和定时任务,直到所有任务都被处理完
        // 确保总是在执行任务
        // 剩下的时间都去处理普通任务和定时任务
        ranTasks = runAllTasks();
    }
//若ioRatio不为100
} else if (strategy > 0) { // 如果需要去处理IO事件
    // 记录处理IO事件前的时间
    final long ioStartTime = System.nanoTime();
    try {
        // 处理IO事件
        processSelectedKeys();
    } finally {
        // 确保一直执行任务
        // ioTime为处理IO事件所耗费的时间
        final long ioTime = System.nanoTime() - ioStartTime;
        // 计算处理其他任务的时间
        // 超过设定的时间后,将会停止任务的执行,会在下一次循环中再继续执行
        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
    }
} else { // 没有IO事件需要处理
    // This will run the minimum number of tasks
    // 直接处理普通和定时任务
    ranTasks = runAllTasks(0); 
}

 

处理事件

1、NioEventLoop.processSelectedKeys() 方法处理 I/O 事件

private void processSelectedKeys() {
    // 若selectedKeys != null,说明selectedKeys基于数组实现
    if (selectedKeys != null) {
        // 处理各种IO事件
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

2、processSelectedKeysOptimized()

private void processSelectedKeysOptimized() {
    //for循环,遍历基于数组的SelectedKey
    for (int i = 0; i < selectedKeys.size; ++i) {
        // 获取SelectionKey
        final SelectionKey k = selectedKeys.keys[i];
        // null out entry in the array to allow to have it GC'ed once the Channel close
        // See https://github.com/netty/netty/issues/2363
        selectedKeys.keys[i] = null;

        // 获取SelectionKey上的附件(其附件在register时添加),即NioServerSocketChannel
        final Object a = k.attachment();
        //如果附件继承自AbstractNioChannel
        if (a instanceof AbstractNioChannel) {
            // 处理事件,传入附件NioServerSocketChannel
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        if (needsToSelectAgain) {
            // null out entries in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            selectedKeys.reset(i + 1);

            selectAgain();
            i = -1;
        }
    }
}

3、真正处理各种事件的方法:processSelectedKey,获取 SelectionKey 事件,然后进行相应处理

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    if (!k.isValid()) {
        final EventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable ignored) {
            // If the channel implementation throws an exception because there is no event loop, we ignore this
            // because we are only trying to determine if ch is registered to this event loop and thus has authority
            // to close ch.
            return;
        }
        // Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop
        // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
        // still healthy and should not be closed.
        // See https://github.com/netty/netty/issues/5125
        if (eventLoop == this) {
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
        }
        return;
    }

    try {
        int readyOps = k.readyOps();
        // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
        // the NIO JDK channel implementation may throw a NotYetConnectedException.
        //是否为连接事件
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
            // See https://github.com/netty/netty/issues/924
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);

            unsafe.finishConnect();
        }

        // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
        //是否为写事件
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
            ch.unsafe().forceFlush();
        }

        // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
        // to a spin loop
        //是否为读事件或接收事件
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

 

NIO 处理 Accept 事件

1、流程

(1)select() 阻塞线程,直到事件发生

(2)遍历 selectionKeys

(3)获取一个 key,判断事件类型是否为 Accept

(4)创建 SocketChannel,设置为非阻塞,并创建 NioSocketChannel

(5)将 SocketChannel 注册到 Selector 中,不关注事件,附件为 NioSocketChannel

(6)关注 SelectionKey 的 read 事件

2、SocketChannel 的创建与注册

(1)发生 Accept 事件后,会执行 NioEventLoop.run() 的 if 分支

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
	unsafe.read();
}

(2)NioMessageUnsafe.read()

public void read() {
    ……
    try {
        try {
            do {
                // doReadMessages中执行accept,获取SocketChannel,并创建NioSocketChannel,作为消息放入readBuf
                // readBuf类型为ArrayList,作用:缓存消息
                // private final List<Object> readBuf = new ArrayList<Object>();
                int localRead = doReadMessages(readBuf);
                ……
                // 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;
            // 让pipeline上的handler处理
            // 触发ServerBootstrapAcceptor.channelRead
            pipeline.fireChannelRead(readBuf.get(i));
        }
        ……
    } finally {
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

(3)NioSocketChannel.doReadMessages

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    // 传入原生SocketChannel,处理accpet事件,创建SocketChannel,将其设置为非阻塞
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
        if (ch != null) {
            // 创建NioSocketChannel,与原生SocketChannel建立联系,作为消息存入readBuf中
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
       ……
    }
    return 0;
}

(4)SocketUtils.accept

public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
    try {
        return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
            @Override
            public SocketChannel run() throws IOException {
                // 处理accpet事件,创建SocketChannel
                return serverSocketChannel.accept();
            }
        });
    } catch (PrivilegedActionException e) {
        throw (IOException) e.getCause();
    }
}

(5)ServerBootstrapAcceptor.channelRead

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到新的NioEventLoop
        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);
    }
}

(6)AbstractUnsafe.register 方法,将 SocketChannel 注册到 Selector 中,过程与启动流程中的register 过程类似

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ……
    AbstractChannel.this.eventLoop = eventLoop;
    //判断当前线程是否为EventLoop的线程
    if (eventLoop.inEventLoop()) {
        //若是,直接注册
        register0(promise);
    } else {
        //若否,则封装为新任务,派发到新的EventLoop进行注册
        try {
            // 这行代码完成 NIO Boss 线程到 NIO Worker 线程的切换
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ……
        }
    }
}

(7)AbstractChannel.AbstractUnsafe.register0

private void register0(ChannelPromise promise) {
    try {
        ……
        // 该方法将SocketChannel注册到Selector中
        doRegister();
        
        //调用ChannelInitializer(NioSocketChannel的初始化器),添加Handler
        //调用前:head -> ChannelInitializer -> tail
        pipeline.invokeHandlerAddedIfNeeded();
        //调用前:head -> 自定义Handler -> tail
        
        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        
        if (isActive()) {
            if (firstRegistration) {
                // 触发pipeline上active事件
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

(8)AbstractNioChannel.doRegister

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // 将原生SocketChannel注册到当前EventLoop的Selector中,不关注任何事件,以当前NioSocketChannel作为附件
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            ……
        }
    }
}

(8)HeadContext.channelActive

public void channelActive(ChannelHandlerContext ctx) {
    ctx.fireChannelActive();
    // 关注read事件,此处NioSocketChannel的read,只为触发channel的事件注册,不涉及数据读取
    readIfIsAutoRead();
}

(9)readIfIsAutoRead 进入 AbstractNioChannel.doBeginRead,通过该方法,SocketChannel 关注 read 事件

protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;
    // 此时interestOps为0
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        // 关注read事件
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

 

处理 Read 事件

1、流程

(1)select() 阻塞,指定事件发生

(2)遍历处理 selectedKeys

(3)获取 SelectionKey,判断事件类型是否为 read

(4)读取

2、发生 Read 事件后,会执行 NioEventLoop.run() 的 if 分支

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
	unsafe.read();
}

3、AbstractNioByteChannel.NioByteUnsafe.read()

@Override
public final void read() {
    // 获得Channel的配置
    final ChannelConfig config = config();
    if (shouldBreakReadReady(config)) {
        clearReadPending();
        return;
    }
    final ChannelPipeline pipeline = pipeline();
	// 根据配置创建ByteBufAllocator
	final ByteBufAllocator allocator = config.getAllocator();
    // 动态分配 byteBuf,IO操作使用直接内存,确定单次读取大小
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
    allocHandle.reset(config);

    ByteBuf byteBuf = null;
    boolean close = false;
    try {
        do {
            // 创建ByteBuf
            byteBuf = allocHandle.allocate(allocator);
            // 读取内容,放入ByteBUf中
            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 {
         // Check if there is a readPending which was not processed yet.
         // This could be for two reasons:
         // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
         // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
         //
         // See https://github.com/netty/netty/issues/2254
        if (!readPending && !config.isAutoRead()) {
            removeReadOp();
        }
    }
}

4、DefaultMaxMessagesRecvByteBufAllocator.MaxMessageHandle.continueReading(io.netty.util.UncheckedBooleanSupplier) 

public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
    return 
        // 一般为true
        config.isAutoRead() &&
        // respectMaybeMoreData默认为true
        // maybeMoreDataSupplier的逻辑是如果预期读取字节与实际读取字节相等,返回true
        (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
        // 小于最大次数,maxMessagePerRead默认16
        totalMessages < maxMessagePerRead &&
        // 实际读到了数据
        totalBytesRead > 0;
}
posted @   半条咸鱼  阅读(72)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示