Netty 服务端:新连接接入

  本文主要分析服务端新连接的接入过程,主要分为以下 3 各步骤:

  1. select 操作;
  2. processSelectedKeys 操作;
  3. runAllTasks 操作。

1. select 操作

  在分析 select 操作前,先要回顾一下 NioEventLoop 的 run()方法及其父类 SingleThreadEventExecutor 的 execute(Runnable task)方法。

 1 @Override
 2 public void execute(Runnable task) {
 3     if (task == null) {
 4         throw new NullPointerException("task");
 5     }
 6     //判断是否是 netty 线程添加的任务
 7     boolean inEventLoop = inEventLoop();
 8     if (inEventLoop) {
 9         addTask(task);//是的话直接添加任务到队列
10     } else {
11         //不是的话,可能要创建 netty 线程
12         startThread();
13         //然后添加任务到队列
14         addTask(task);
15         if (isShutdown() && removeTask(task)) {
16             reject();
17         }
18     }
19     // 构造方法中将 addTaskWakesUp 置 false
20     // wakesUpForTask(task) 直接返回 true
21     if (!addTaskWakesUp && wakesUpForTask(task)) {
22         // 所以 wakeup()方法肯定会被调用
23         wakeup(inEventLoop);
24     }
25 }

  这里的 wakeup(boolean inEventLoop)方法分析 NioEventLoop 中的:

 1 @Override
 2 protected void wakeup(boolean inEventLoop) {
 3     // 如果是非 Netty 线程添加任务,那么 wakenUp.compareAndSet(false ,true)
 4     // 成功的话,就会调用 selector 的 wakeup()方法
 5     if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
 6         // 回顾一下 selector 的 wakeup()方法
 7         // 1. 当 selector 在执行 select 操作时,调用它的 wakeup()方法,
 8         // 那么当前的 select 操作会立刻返回已就绪的事件集
 9         // 2. 如果提前调用 selector 的 wakeup()方法(一次或者多次都是一样的)
10         // 那么下一次的 select 操作会直接返回(应该是没有就绪事件)
11         // 关于 selector 的 wakeup()方法,可以看一下文末的参考资料
12         selector.wakeup();
13     }
14 }

   接下来再 分析以下 NioEventLoop 中的 run()方法:

 1 protected void run() {
 2     for (;;) {
 3         try {
 4             // 计算 select 策略,当前有任务时,会进行一次 selectNow 操作返回就绪的 key 个数(大于等于 0)
 5             // SelectStrategy.CONTINUE 值是 -2,SelectStrategy.SELECT 的值是 -1
 6             // (这里的 SelectStrategy.CONTINUE 感觉不会匹配到)
 7             // 显然 switch 中没有匹配项,直接跳出 switch
 8             // 无任务时,则直接返回 SelectStrategy.SELECT
 9             switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
10                 case SelectStrategy.CONTINUE:
11                     continue;
12                 case SelectStrategy.SELECT:
13                     //当没有可处理的任务时,直接进行 select 操作
14                     // wakenUp.getAndSet(false) 返回的是 oldValue,由于默认值是 false
15                     // 所以第一次返回的是 false,需要注意的是,只有在这个地方才将 wakenUp 置为 false
16                     select(wakenUp.getAndSet(false));
17 
18                     if (wakenUp.get()) {
19                         selector.wakeup();
20                     }
21                 default:
22                     // fallthrough
23             }
24             cancelledKeys = 0;
25             needsToSelectAgain = false;
26             final int ioRatio = this.ioRatio;
27             //根据比例来处理 IO 事件和任务
28             if (ioRatio == 100) {
29                 //...
30             } else {
31                 final long ioStartTime = System.nanoTime();
32                 try {
33                     processSelectedKeys();
34                 } finally {
35                     // Ensure we always run tasks.
36                     // 计算出处理 IO 事件的时间,然后根据比例算出执行任务的时间
37                     final long ioTime = System.nanoTime() - ioStartTime;
38                     runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
39                 }
40             }
41         } catch (Throwable t) {
42             handleLoopException(t);
43         }
44         //...
45     }
46 }

  wakenUp 是用来减少调用 selector 的 wakeup()方法,关于 wakeup()方法的实现细节,参考文末的资料。接下来分析一下 key 的处理:

 1 private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
 2     for (int i = 0;; i ++) {
 3         final SelectionKey k = selectedKeys[i];
 4         if (k == null) {
 5             break;
 6         }
 7         // null out entry in the array to allow to have it GC'ed once the Channel close
 8         // See https://github.com/netty/netty/issues/2363
 9         // 置为 null,参考上面的一行的注释
10         selectedKeys[i] = null;
11 
12         // 在前面 AbstractNioChannel 中的 doRegister()方法中,注册的时候传入的参数是 this 
13         // 接入连接时,这个 a 就是 AbstractNioChannel 对象
14         final Object a = k.attachment();
15 
16         if (a instanceof AbstractNioChannel) {
17             processSelectedKey(k, (AbstractNioChannel) a);
18         } else {
19             //这里暂时不熟悉
20             @SuppressWarnings("unchecked")
21             NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
22             processSelectedKey(k, task);
23         }
24         //...
25     }
26 }

  接下来分析一下 processSelectedKey()方法:

 1 private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
 2     //取出 channel 对应的 unsafe
 3     final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
 4     if (!k.isValid()) {
 5         //... 当 key 无效时,关闭 channel 等操作
 6     }
 7 
 8     try {
 9         int readyOps = k.readyOps();
10         // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
11         // the NIO JDK channel implementation may throw a NotYetConnectedException.
12         if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
13             // 连接就绪事件只需要处理一次就行了,否则后续的 select()操作会一直立刻返回
14             // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
15             // See https://github.com/netty/netty/issues/924
16             int ops = k.interestOps();
17             ops &= ~SelectionKey.OP_CONNECT;
18             k.interestOps(ops);
19             // 最后调用 SocketChannel 的 finishConnect()方法
20             unsafe.finishConnect();
21         }
22 
23         // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
24         if ((readyOps & SelectionKey.OP_WRITE) != 0) {
25             // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
26             ch.unsafe().forceFlush();
27         }
28 
29         // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
30         // to a spin loop
31         if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
32             // read 和 accept
33             unsafe.read();
34             if (!ch.isOpen()) {
35                 // Connection already closed - no need to handle write.
36                 return;
37             }
38         }
39     } catch (CancelledKeyException ignored) {
40         unsafe.close(unsafe.voidPromise());
41     }
42 }

  由于新连接的接入是一个就绪的 ACCEPT 事件,所以分析一下 unsafe.read(),这个 unsafe 对象是创建服务端 Channel 时创建的,是一个 NioMessageUnsafe 对象,它的 read()方法中有一行:

1 int localRead = doReadMessages(readBuf);

  该方法的实现选择 NioServerSocketChannel 中的:

 1 @Override
 2 protected int doReadMessages(List<Object> buf) throws Exception {
 3     // 接入连接
 4     SocketChannel ch = javaChannel().accept();
 5     try {
 6         if (ch != null) {
 7             // 向 Object 列表中加入封装后的 NioSocketChannel 对象
 8             buf.add(new NioSocketChannel(this, ch));
 9             return 1;
10         }
11     } catch (Throwable t) {
12         logger.warn("Failed to create a new channel from an accepted socket.", t);
13 
14         try {
15             ch.close();
16         } catch (Throwable t2) {
17             logger.warn("Failed to close a socket.", t2);
18         }
19     }
20 
21     return 0;
22 }

  回到 NioMessageUnsafe 的 read()方法,doReadMessages()方法返回了 NioSocketChannel 列表,接下来的一行:

1 pipeline.fireChannelRead(readBuf.get(i));

  就会调用服务端 Channel 中的 handler 链,首先是用户添加的 handler,最后会找到前面说过的 ServerBootstrapAcceptor,它的 channelRead()方法中的 msg 参数实际上是 Channel 对象。

  接下来对这个客户端的 Channel 的处理与服务端 Channel 的处理过程基本类似。

猜你喜欢

转载自www.cnblogs.com/magexi/p/10318143.html