Netty之Channel(四)close操作

关闭操作,可能是客户端/服务端主动关闭,也可能是异常关闭。
Netty NIO Channel的close操作分成客户端和服务端Channel两种关闭。

  1. 客户端关闭NioSocketChannel,断开和服务器的连接;服务端关闭NioSocketChannel,断开和客户端的连接。
  2. 服务端关闭NioServerSocketChannel,取消端口绑定,关闭服务。

NioSocketChannel的close() 方法,应用程序里可以主动关闭 NioSocketChannel 通道:

// AbstractChannel.java
@Override
public ChannelFuture close() {
    return pipeline.close();
}

NioSocketChannel 继承 AbstractChannel 抽象类,那么close() 方法实际是 AbstractChannel 实现的。
在方法内部,会调用对应的 ChannelPipeline的close() 方法,将 close 事件在 pipeline 上传播。而 close 事件属于 Outbound 事件(之前文章有提回去看看呗),所以会从 tail 节点开始,最终传播到 head 节点,使用 Unsafe 进行关闭:

// DefaultChannelPipeline.java
@Override
public final ChannelFuture close() {
    return tail.close();
}

// TailContext.java
@Override // FROM AbstractChannelHandlerContext.java 。因为 TailContext 继承 AbstractChannelHandlerContext 抽象类,该方法是它实现的。
public ChannelFuture close() {
    return close(newPromise());
}

// HeadContext.java
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    unsafe.close(promise);
}

AbstractUnsafe的close() 方法,关闭 Channel:

@Override
public final void close(final ChannelPromise promise) {
    assertEventLoop();

    // 关闭
    close(promise, CLOSE_CLOSED_CHANNEL_EXCEPTION, CLOSE_CLOSED_CHANNEL_EXCEPTION, false);
}

  private void close(final ChannelPromise promise, final Throwable cause, final ClosedChannelException closeCause, final boolean notify) {
      // 设置 Promise 不可取消
      if (!promise.setUncancellable()) {
          return;
      }
  
      // 若 关闭已经标记初始化,此时可能已经关闭完成
      if (closeInitiated) {
          // 关闭已经完成,直接通知 Promise 对象
          if (closeFuture.isDone()) {
              // Closed already.
              safeSetSuccess(promise);
          // 关闭未完成,通过监听器通知 Promise 对象
          } else if (!(promise instanceof VoidChannelPromise)) { // Only needed if no VoidChannelPromise.
              // This means close() was called before so we just register a listener and return
              closeFuture.addListener(new ChannelFutureListener() {
                  @Override
                  public void operationComplete(ChannelFuture future) throws Exception {
                      promise.setSuccess();
                  }
              });
          }
          return;
      }
  
      // 标记关闭已经初始化
      closeInitiated = true;
  
      // 获得 Channel 是否激活
      final boolean wasActive = isActive();
      // 标记 outboundBuffer 为空
      final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
      this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
      // 执行准备关闭
      Executor closeExecutor = prepareToClose();
      // 若 closeExecutor 非空
      if (closeExecutor != null) {
          closeExecutor.execute(new Runnable() {
              @Override
              public void run() {
                  try {
                      // 在 closeExecutor 中,执行关闭
                      // Execute the close.
                      doClose0(promise);
                  } finally {
                      // 在 EventLoop 中,执行
                      // Call invokeLater so closeAndDeregister is executed in the EventLoop again!
                      invokeLater(new Runnable() {
                          @Override
                          public void run() {
                              if (outboundBuffer != null) {
                                  // 写入数据( 消息 )到对端失败,通知相应数据对应的 Promise 失败。
                                  // Fail all the queued messages
                                  outboundBuffer.failFlushed(cause, notify);
                                  // 关闭内存队列
                                  outboundBuffer.close(closeCause);
                              }
                              // 执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
                              fireChannelInactiveAndDeregister(wasActive);
                          }
                      });
                  }
              }
          });
      // 若 closeExecutor 为空
      } else {
          try {
              // 执行关闭
              // Close the channel and fail the queued messages in all cases.
              doClose0(promise);
          } finally {
              if (outboundBuffer != null) {
                  // 写入数据( 消息 )到对端失败,通知相应数据对应的 Promise 失败。
                  // Fail all the queued messages.
                  outboundBuffer.failFlushed(cause, notify);
                  // 关闭内存队列
                  outboundBuffer.close(closeCause);
              }
          }
          // 正在 flush 中,在 EventLoop 中执行执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
          if (inFlush0) {
              invokeLater(new Runnable() {
                  @Override
                  public void run() {
                      fireChannelInactiveAndDeregister(wasActive);
                  }
              });
          // 不在 flush 中,直接执行执行取消注册,并触发 Channel Inactive 事件到 pipeline 中
          } else {
              fireChannelInactiveAndDeregister(wasActive);
          }
      }
  }

方法参数 cause、closeCause ,关闭的“原因”。对于 close 操作来说,无论是正常关闭,还是异常关闭,都通过使用 Exception 来表示来源。在 AbstractChannel 类中,枚举了所有来源:

// AbstractChannel.java
private static final ClosedChannelException FLUSH0_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
        new ClosedChannelException(), AbstractUnsafe.class, "flush0()");
private static final ClosedChannelException ENSURE_OPEN_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
        new ClosedChannelException(), AbstractUnsafe.class, "ensureOpen(...)");
private static final ClosedChannelException CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
        new ClosedChannelException(), AbstractUnsafe.class, "close(...)");
private static final ClosedChannelException WRITE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
        new ClosedChannelException(), AbstractUnsafe.class, "write(...)");
private static final NotYetConnectedException FLUSH0_NOT_YET_CONNECTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
        new NotYetConnectedException(), AbstractUnsafe.class, "flush0()");

回到AbstractUnsafe的close中调用prepareToClose() 方法,执行准备关闭。

 @Override
 protected Executor prepareToClose() {
     try {
         if (javaChannel().isOpen() && config().getSoLinger() > 0) {
            // We need to cancel this key of the channel so we may not end up in a eventloop spin
             // because we try to read or write until the actual close happens which may be later due
             // SO_LINGER handling.
             // See https://github.com/netty/netty/issues/4449
             doDeregister();
             // 返回 GlobalEventExecutor 对象
             return GlobalEventExecutor.INSTANCE;
         }
     } catch (Throwable ignore) {
         // Ignore the error as the underlying channel may be closed in the meantime and so
         // getSoLinger() may produce an exception. In this case we just return null.
        // See https://github.com/netty/netty/issues/4449
     }
     return null;
 }

上文配置StandardSocketOptions.SO_LINGER 大于 0。
(Socket 参数,关闭 Socket 的延迟时间,Netty 默认值为 -1 ,表示禁用该功能。

  • -1 表示 socket.close() 方法立即返回,但 OS 底层会将发送缓冲区全部发送到对端。
  • 0 表示 socket.close() 方法立即返回,OS 放弃发送缓冲区的数据直接向对端发送RST包,对端收到复位错误。
  • 非 0 整数值表示调用 socket.close() 方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。)

所以我们知道,如果大于0,在真正关闭Channel,需要阻塞直到延迟时间到或发送缓冲区中的数据发送完毕。
如果在 EventLoop 中执行真正关闭 Channel 的操作,则会阻塞 EventLoop 的线程。所以上面有返回 GlobalEventExecutor.INSTANCE 对象,作为执行真正关闭 Channel 的操作的执行器,他有自己的线程。
调用doDeregister()方法的原因是,SO_LINGER 大于 0 时,真正关闭 Channel ,需要阻塞直到延迟时间到或发送缓冲区中的数据发送完毕。
如果不取消该 Channel 的 SelectionKey.OP_READ 事件的感兴趣,就会不断触发读事件,导致 CPU 空轮询。
在 Channel 关闭时,会自动触发 SelectionKey.OP_READ 事件。而且,会不断不断不断的触发,如果不进行取消 SelectionKey.OP_READ 事件的感兴趣,那么就…hh

AbstractUnsafe的doDeregister() 方法,执行取消注册:

@Override
protected void doDeregister() throws Exception {
    eventLoop().cancel(selectionKey());
}

调用cancel(SelectionKey key) 方法,取消 SelectionKey 。

AbstractUnsafe的doClose0(ChannelPromise promise) 方法,执行真正的关闭:

 private void doClose0(ChannelPromise promise) {
     try {
         // 执行关闭
         doClose();
         // 通知 closeFuture 关闭完成
         closeFuture.setClosed();
         // 通知 Promise 关闭成功
         safeSetSuccess(promise);
     } catch (Throwable t) {
         // 通知 closeFuture 关闭完成
         closeFuture.setClosed();
         // 通知 Promise 关闭异常
         safeSetFailure(promise, t);
     }
 }

NioSocketChannel的doClose() 方法,执行 Java 原生 NIO SocketChannel 关闭:

 @Override
 protected void doClose() throws Exception {
     // 执行父类关闭方法
     super.doClose();
     // 执行 Java 原生 NIO SocketChannel 关闭
     javaChannel().close();
 }

AbstractNioChannel中doClose() 方法,执行父类关闭方法:

@Override
protected void doClose() throws Exception {
    // 通知 connectPromise 异常失败
    ChannelPromise promise = connectPromise;
    if (promise != null) {
        // Use tryFailure() instead of setFailure() to avoid the race against cancel().
        promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
        connectPromise = null;
    }

    // 取消 connectTimeoutFuture 等待
    ScheduledFuture<?> future = connectTimeoutFuture;
    if (future != null) {
        future.cancel(false);
        connectTimeoutFuture = null;
    }
}

AbstractUnsafe中fireChannelInactiveAndDeregister(boolean wasActive) 方法,执行取消注册,并触发 Channel Inactive 事件到 pipeline 中:

private void fireChannelInactiveAndDeregister(final boolean wasActive) {
    deregister(voidPromise() , wasActive && !isActive() ); 
}

  private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
     // 设置 Promise 不可取消
     if (!promise.setUncancellable()) {
         return;
      }
  
      // 不处于已经注册状态,直接通知 Promise 取消注册成功。
      if (!registered) {
          safeSetSuccess(promise);
          return;
      }
  
      // As a user may call deregister() from within any method while doing processing in the ChannelPipeline,
      // we need to ensure we do the actual deregister operation later. This is needed as for example,
      // we may be in the ByteToMessageDecoder.callDecode(...) method and so still try to do processing in
      // the old EventLoop while the user already registered the Channel to a new EventLoop. Without delay,
      // the deregister operation this could lead to have a handler invoked by different EventLoop and so
      // threads.
      //
      // See:
      // https://github.com/netty/netty/issues/4435
      invokeLater(new Runnable() {
          @Override
          public void run() {
              try {
                  // 执行取消注册
                  doDeregister();
              } catch (Throwable t) {
                  logger.warn("Unexpected exception occurred while deregistering a channel.", t);
              } finally {
                  // 触发 Channel Inactive 事件到 pipeline 中
                  if (fireChannelInactive) {
                      pipeline.fireChannelInactive();
                  }
  
                  // Some transports like local and AIO does not allow the deregistration of
                  // an open channel.  Their doDeregister() calls close(). Consequently,
                  // close() calls deregister() again - no need to fire channelUnregistered, so check
                  // if it was registered.
                  if (registered) {
                      // 标记为未注册
                      registered = false;
                      // 触发 Channel Unregistered 事件到 pipeline 中
                      pipeline.fireChannelUnregistered();
                  }
  
                  // 通知 Promise 取消注册成功。
                  safeSetSuccess(promise);
              }
          }
      });
  }

嘿嘿,close()方法们的实现都很相似,所以在NioSocketChannel 和 NioServerSocketChannel 差异的关闭逻辑实现是通过给他们配置不同的 ChannelHandler 实现类。

在 Unsafe 接口上定义了 closeForcibly() 方法,立即关闭 Channel ,并且不触发 pipeline 上的任何事件。(仅仅用于 Channel 注册到 EventLoop 上失败的情况下)

/**
 * Closes the {@link Channel} immediately without firing any events.  Probably only useful
 * when registration attempt failed.
 */
void closeForcibly();

AbstractUnsafe 对该接口方法,实现代码如下:

@Override
public final void closeForcibly() {
    assertEventLoop();

    try {
        doClose();
    } catch (Exception e) {
        logger.warn("Failed to close a channel.", e);
    }
}

服务端处理客户端主动关闭连接:
在客户端主动关闭时,服务端会收到一个 SelectionKey.OP_READ 事件的就绪,在调用客户端对应在服务端的 SocketChannel 的 read() 方法会返回 -1 ,从而实现在服务端关闭客户端的逻辑。在 Netty 的实现,在 NioByteUnsafe的read() 方法中。


调用closeOnRead(ChannelPipeline pipeline) 方法,关闭客户端的连接:

 private void closeOnRead(ChannelPipeline pipeline) {
     if (!isInputShutdown0()) {
         // 开启连接半关闭
         if (isAllowHalfClosure(config())) {
             // 关闭 Channel 数据的读取
             shutdownInput();
             // 触发 ChannelInputShutdownEvent.INSTANCE 事件到 pipeline 中
             pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
         } else {
             close(voidPromise());
         }
     } else {
         // 标记 inputClosedSeenErrorOnRead 为 true
         inputClosedSeenErrorOnRead = true;
         // 触发 ChannelInputShutdownEvent.INSTANCE 事件到 pipeline 中
         pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
     }
 }

调用 NioSocketChannel中isInputShutdown0() 方法,判断是否关闭 Channel 数据的读取:

// NioSocketChannel.java
@Override
protected boolean isInputShutdown0() {
    return isInputShutdown();
}

@Override
public boolean isInputShutdown() {
    return javaChannel().socket().isInputShutdown() || !isActive();
}

// java.net.Socket.java
private boolean shutIn = false;
/**
 * Returns whether the read-half of the socket connection is closed.
 *
 * @return true if the input of the socket has been shutdown
 * @since 1.4
 * @see #shutdownInput
 */
public boolean isInputShutdown() {
    return shutIn;
}

isAllowHalfClosure()方法,判断是否开启连接半关闭的功能:

// AbstractNioByteChannel.java
private static boolean isAllowHalfClosure(ChannelConfig config) {
    return config instanceof SocketChannelConfig &&
            ((SocketChannelConfig) config).isAllowHalfClosure();
}

可通过 ALLOW_HALF_CLOSURE 配置项开启。Netty 参数,一个连接的远端关闭时本地端是否关闭,默认值为 false 。

  1. 值为 false时,连接自动关闭。
  2. 值为 true 时,触发 ChannelInboundHandler 的userEventTriggered() 方法,事件 ChannelInputShutdownEvent

调用 NioSocketChannel的shutdownInput() 方法,关闭 Channel 数据的读取:

@Override
public ChannelFuture shutdownInput() {
    return shutdownInput(newPromise());
}

@Override
public ChannelFuture shutdownInput(final ChannelPromise promise) {
    EventLoop loop = eventLoop();
    if (loop.inEventLoop()) {
        shutdownInput0(promise);
    } else {
        loop.execute(new Runnable() {
            @Override
            public void run() {
                shutdownInput0(promise);
            }
        });
    }
    return promise;
}

private void shutdownInput0(final ChannelPromise promise) {
    try {
        // 关闭 Channel 数据的读取
        shutdownInput0();
        // 通知 Promise 成功
        promise.setSuccess();
    } catch (Throwable t) {
        // 通知 Promise 失败
        promise.setFailure(t);
    }
}

private void shutdownInput0() throws Exception {
    // 调用 Java NIO Channel 的 shutdownInput 方法
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().shutdownInput();
    } else {
        javaChannel().socket().shutdownInput();
    }
}

在标记 inputClosedSeenErrorOnRead = true 后,在 NioByteUnsafe中read() 方法中,会主动对 SelectionKey.OP_READ 的感兴趣,避免空轮询。上面用提到哦~

// AbstractNioByteUnsafe.java
public final void read() {
    final ChannelConfig config = config();
    // 若 inputClosedSeenErrorOnRead = true ,移除对 SelectionKey.OP_READ 事件的感兴趣。
    if (shouldBreakReadReady(config)) {
        clearReadPending(); // 移除对 SelectionKey.OP_READ 事件的感兴趣
        return;
    }
}

// AbstractNioByteChannel.java
final boolean shouldBreakReadReady(ChannelConfig config) {
    return isInputShutdown0() && (inputClosedSeenErrorOnRead || !isAllowHalfClosure(config));
}

差不多就这样啦。

发布了46 篇原创文章 · 获赞 6 · 访问量 3847

猜你喜欢

转载自blog.csdn.net/weixin_43257196/article/details/104865942