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);
        ...
    }
}
  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值