Netty之Channel(三)flush操作

flush()方法,刷新内存队列,将数据写入到对端。flush()方法和write()方法在正常情况下,流程差不多,例如在pipeline中对事件的传播,从tail节点传播到head节点,最后由Unsafe处理。然而两者Unsafe的处理方式不同。

  1. write方法将数据写到内存队列中。
  2. flush方法刷新内存队列,将其中数据写入对端。
    (还有些差异后文提)
    AbstractChannel 对 flush() 方法的实现:
@Override
public Channel flush() {
    pipeline.flush();
    return this;
}

上面调用对应的 ChannelPipeline的flush() 方法,将 flush 事件在 pipeline 上传播。
DefaultChannelPipeline的flush() 方法:

@Override
public final ChannelPipeline flush() {
    tail.flush();
    return this;
}

tail.flush();将flush事件在pipeline中,从尾结点向头结点传播:
TailContext 对 flush() 方法的实现,是从 AbstractChannelHandlerContext 抽象类继承

 @Override
 public ChannelHandlerContext flush() {
     // 获得下一个 Outbound 节点
     final AbstractChannelHandlerContext next = findContextOutbound();
     EventExecutor executor = next.executor();
     // 在 EventLoop 的线程中
     if (executor.inEventLoop()) {
         // 执行 flush 事件到下一个节点
         next.invokeFlush();
     // 不在 EventLoop 的线程中
     } else {
         // 创建 flush 任务
         Runnable task = next.invokeFlushTask;
         if (task == null) {
             next.invokeFlushTask = task = new Runnable() {
                 @Override
                 //不在EventLoop线程中
                 public void run() {
                     next.invokeFlush();
                 }
             };
         }
         // 提交到 EventLoop 的线程中,执行该任务
         safeExecute(executor, task, channel().voidPromise(), null);
     }
 
     return this;
 }

在 pipeline 中,flush 事件最终会到达 HeadContext 节点。而 HeadContext 的 flush() 方法会处理该事件:

@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
    unsafe.flush();
}

最终会传播 flush 事件到 head 节点,刷新内存队列,将其中的数据写入到对端。
在这之前和write操作非常相似。

在上面方法内部,会调用 AbstractUnsafe的flush() 方法,刷新内存队列,将其中的数据写入到对端:

 @Override
 public final void flush() {
     assertEventLoop();
 
     // 内存队列为 null ,一般是 Channel 已经关闭,所以直接返回。
     ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
     if (outboundBuffer == null) {
         return;
     }
 
     // 标记内存队列开始 flush
     outboundBuffer.addFlush();
     // 执行 flush
     flush0();
 }

flush0():

/**
 * 是否正在 flush 中,即正在调用 {@link #flush0()} 中
 */
private boolean inFlush0;

  @SuppressWarnings("deprecation")
  protected void flush0() {
      // 正在 flush 中,所以直接返回。
      if (inFlush0) {
          // Avoid re-entrance
          return;
      }
  //if里
      // 内存队列为 null ,一般是 Channel 已经关闭,所以直接返回。
      // 内存队列为空,无需 flush ,所以直接返回
      final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
      if (outboundBuffer == null || outboundBuffer.isEmpty()) {
          return;
      }
  
      // 标记正在 flush 中。
      inFlush0 = true;
  
      // 若未激活,通知 flush 失败
      // Mark all pending write requests as failure if the channel is inactive.
      if (!isActive()) {
          try {
              if (isOpen()) {
                  outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true);
              } else {
                  // Do not trigger channelWritabilityChanged because the channel is closed already.
                  outboundBuffer.failFlushed(FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
              }
          } finally {
              // 标记不在 flush 中。
              inFlush0 = false;
          }
          return;
      }
  
      // 执行真正的写入到对端
      try {
          doWrite(outboundBuffer);
      } catch (Throwable t) {
          
          if (t instanceof IOException && config().isAutoClose()) {
              /**
               * Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of
               * failing all flushed messages and also ensure the actual close of the underlying transport
               * will happen before the promises are notified.
               *
               * This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()}
               * may still return {@code true} even if the channel should be closed as result of the exception.
               */
              close(voidPromise(), t, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
          } else {
              try {
                  shutdownOutput(voidPromise(), t);
              } catch (Throwable t2) {
                  close(voidPromise(), t2, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
              }
          }
      } finally {
          // 标记不在 flush 中。
          inFlush0 = false;
      }
  }

实际上,AbstractNioUnsafe 重写了 flush0() 方法:

@Override
protected final void flush0() {
    // Flush immediately only when there's no pending flush.
    // If there's a pending flush operation, event loop will call forceFlush() later,
    // and thus there's no need to call it now.
    if (!isFlushPending()) {
        super.flush0();
    }
}

在执行父类 AbstractUnsafe 的 flush0() 方法时,先调用 AbstractNioUnsafe的isFlushPending() 判断,是否已经处于 flush 准备中:

private boolean isFlushPending() {
    SelectionKey selectionKey = selectionKey();
    return selectionKey.isValid() // 合法
            && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0; // 对 SelectionKey.OP_WRITE 事件不感兴趣。
}

若在异常情况下,(Channel在大多数情况下可写,所以不需要专门注册SelectionKey.OP_WRITE 事件,所以在Netty的实现中,默认Channel可写。当写入失败的时候,再去注册SelectionKey.OP_WRITE 事件)flush()方法若写入数据到Channel失败,就会通过注册SelectionKey.OP_WRITE 事件,然后再轮训到Channel可写时,再“回调”forceFlush()方法。(这里和write()有些差异)


AbstractChannel的doWrite(ChannelOutboundBuffer in) 抽象方法,执行真正的写入到对端。定义在 AbstractChannel 抽象类中:

扫描二维码关注公众号,回复: 10795971 查看本文章
/**
 * Flush the content of the given buffer to the remote peer.
 */
protected abstract void doWrite(ChannelOutboundBuffer in) throws Exception;

NioSocketChannel 对该抽象方法,实现代码如下:

 @Override
 protected void doWrite(ChannelOutboundBuffer in) throws Exception {
     SocketChannel ch = javaChannel();
     // 获得自旋写入次数
     int writeSpinCount = config().getWriteSpinCount();
     do {
         // 内存队列为空,结束循环,直接返回
         if (in.isEmpty()) {
             // 取消对 SelectionKey.OP_WRITE 的感兴趣
             // All written so clear OP_WRITE
             clearOpWrite();
             // Directly return here so incompleteWrite(...) is not called.
             return;
         }
 
         // 获得每次写入的最大字节数
         // Ensure the pending writes are made of ByteBufs only.
         int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
         // 从内存队列中,获得要写入的 ByteBuffer 数组
         ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
         // 写入的 ByteBuffer 数组的个数
         int nioBufferCnt = in.nioBufferCount();
 
         // 写入 ByteBuffer 数组,到对端
         // Always us nioBuffers() to workaround data-corruption.
         // See https://github.com/netty/netty/issues/2761
         switch (nioBufferCnt) {
             case 0:
                 //  TODO 1014 扣 doWrite0 的细节
                 // We have something else beside ByteBuffers to write so fallback to normal writes.
                 writeSpinCount -= doWrite0(in);
                 break;
             case 1: {
                 // Only one ByteBuf so use non-gathering write
                 // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
                 // to check if the total size of all the buffers is non-zero.
                 ByteBuffer buffer = nioBuffers[0];
                 int attemptedBytes = buffer.remaining();
                 // 执行 NIO write 调用,写入单个 ByteBuffer 对象到对端
                 final int localWrittenBytes = ch.write(buffer);
                 // 写入字节小于等于 0 ,说明 NIO Channel 不可写,所以注册 SelectionKey.OP_WRITE ,等待 NIO Channel 可写,并返回以结束循环
                 if (localWrittenBytes <= 0) {
                     incompleteWrite(true);
                     return;
                 }
                 // 调整每次写入的最大字节数
                 adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                 // 从内存队列中,移除已经写入的数据( 消息 )
                 in.removeBytes(localWrittenBytes);
                 // 写入次数减一
                 --writeSpinCount;
                 break;
             }
             default: {
                              // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
                 // to check if the total size of all the buffers is non-zero.
                 // We limit the max amount to int above so cast is safe
                 long attemptedBytes = in.nioBufferSize();
                 // 执行 NIO write 调用,写入多个 ByteBuffer 到对端
                 final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                 // 写入字节小于等于 0 ,说明 NIO Channel 不可写,所以注册 SelectionKey.OP_WRITE ,等待 NIO Channel 可写,并返回以结束循环
                 if (localWrittenBytes <= 0) {
                     incompleteWrite(true);
                     return;
                 }
                 // 调整每次写入的最大字节数
                 // Casting to int is safe because we limit the total amount of data in the nioBuffers to int above.
                 adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes, maxBytesPerGatheringWrite);
                 // 从内存队列中,移除已经写入的数据( 消息 )
                 in.removeBytes(localWrittenBytes);
                 // 写入次数减一
                 --writeSpinCount;
                 break;
             }
         }
     } while (writeSpinCount > 0); // 循环自旋写入
 
     // 内存队列中的数据未完全写入,说明 NIO Channel 不可写,所以注册 SelectionKey.OP_WRITE ,等待 NIO Channel 可写
     incompleteWrite(writeSpinCount < 0);
 }

上面在 Channel 不可写的时候,会注册 SelectionKey.OP_WRITE ,等待 NIO Channel 可写。而后会”回调” forceFlush() 方法,该方法内部也会调用 doWrite(ChannelOutboundBuffer in) 方法。所以在完成内部队列的数据向对端写入时候,需要调用 clearOpWrite() 方法:

protected final void clearOpWrite() {
    final SelectionKey key = selectionKey();
    // Check first if the key is still valid as it may be canceled as part of the deregistration
    // from the EventLoop
    // See https://github.com/netty/netty/issues/2104
    if (!key.isValid()) { // 合法
        return;
    }
    final int interestOps = key.interestOps();
    // 若注册了 SelectionKey.OP_WRITE ,则进行取消
    if ((interestOps & SelectionKey.OP_WRITE) != 0) {
        key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
    }
}

调用 AbstractNioByteChannel中incompleteWrite(true) 方法:

protected final void incompleteWrite(boolean setOpWrite) {
    // Did not write completely.
    // true ,注册对 SelectionKey.OP_WRITE 事件感兴趣
    if (setOpWrite) {
        setOpWrite();
    // false ,取消对 SelectionKey.OP_WRITE 事件感兴趣
    } else {
        // It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
        // use our write quantum. In this case we no longer want to set the write OP because the socket is still
        // writable (as far as we know). We will find out next time we attempt to write if the socket is writable
        // and set the write OP if necessary.
        clearOpWrite();

        // Schedule flush again later so other tasks can be picked up in the meantime
        // 立即发起下一次 flush 任务
        eventLoop().execute(flushTask); 
    }
}

setOpWrite 为 true ,调用 setOpWrite() 方法,注册对 SelectionKey.OP_WRITE 事件感兴趣。(说明 Channel 此时是不可写的,那么调用父类 AbstractUnsafe 的 flush0() 方法,也没有意义,所以就不调用。)

protected final void setOpWrite() {
    final SelectionKey key = selectionKey();
    // Check first if the key is still valid as it may be canceled as part of the deregistration
    // from the EventLoop
    // See https://github.com/netty/netty/issues/2104
    if (!key.isValid()) { // 合法
        return;
    }
    final int interestOps = key.interestOps();
    // 注册 SelectionKey.OP_WRITE 事件的感兴趣
    if ((interestOps & SelectionKey.OP_WRITE) == 0) {
        key.interestOps(interestOps | SelectionKey.OP_WRITE);
    }
}

setOpWrite 为 false ,调用 clearOpWrite() 方法,取消对 SelectionKey.OP_WRITE 事件感兴趣。而后,立即发起下一次 flush 任务。

io.netty.channel.ChannelOutboundBuffer 内存队列
在 write 操作时,将数据写到 ChannelOutboundBuffer 中。
在 flush 操作时,将 ChannelOutboundBuffer 的数据写入到对端。

在 write 操作时,将数据写到 ChannelOutboundBuffer 中,都会产生一个 Entry 对象:

/**
 * Recycler 对象,用于重用 Entry 对象
 */
private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
    @Override
    protected Entry newObject(Handle<Entry> handle) {
        return new Entry(handle);
    }
};

/**
 * Recycler 处理器
 */
private final Handle<Entry> handle;
/**
 * 下一条 Entry,通过它,形成 ChannelOutboundBuffer 内部的链式存储每条写入数据的数据结构
 */
Entry next;

/**
 * 写入的消息( 数据 )
 */
Object msg;

/**
 * {@link #msg} 转化的 NIO
ByteBuffer[] bufs;
/**
 * {@link #msg} 转化的 NIO ByteBuffer 对象
 */
ByteBuffer buf;
/**
 * Promise 对象
 */
ChannelPromise promise;
/**
 * 已写入的字节数
 */
long progress;
/**
 * 长度,可读字节数数。
 */
long total;
/**
 * 每个 Entry 预计占用的内存大小,计算方式为消息( {@link #msg} )的字节数 + Entry 对象自身占用内存的大小。
 */
int pendingSize;
/**
 * {@link #msg} 转化的 NIO ByteBuffer 的数量。
 *
 * 当 = 1 时,使用 {@link #buf}
 * 当 > 1 时,使用 {@link #bufs}
 */
int count = -1;
/**
 * 是否取消写入对端
 */
boolean cancelled;

private Entry(Handle<Entry> handle) {
    this.handle = handle;
}

newInstance(Object msg, int size, long total, ChannelPromise promise) 静态方法,创建 Entry 对象:

static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
    // 通过 Recycler 重用对象
    Entry entry = RECYCLER.get();
    // 初始化属性
    entry.msg = msg;
    entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;
    entry.total = total;
    entry.promise = promise;
    return entry;
}

recycle() 方法,回收 Entry 对象,为下次重用该对象。

void recycle() {
    // 重置属性
    next = null;
    bufs = null;
    buf = null;
    msg = null;
    promise = null;
    progress = 0;
    total = 0;
    pendingSize = 0;
    count = -1;
    cancelled = false;
    // 回收 Entry 对象
    handle.recycle(this);
}

recycleAndGetNext() 方法,获得下一个 Entry 对象,并回收当前 Entry 对象

Entry recycleAndGetNext() {
    // 获得下一个 Entry 对象
    Entry next = this.next;
    // 回收当前 Entry 对象
    recycle();
    // 返回下一个 Entry 对象
    return next;
}

cancel() 方法,标记 Entry 对象,取消写入到对端。在 ChannelOutboundBuffer 里,Entry 数组是通过链式的方式进行组织,而当某个 Entry 对象( 节点 )如果需要取消写入到对端,是通过设置 canceled = true 来标记删除:

int cancel() {
    if (!cancelled) {
        // 标记取消
        cancelled = true;
        int pSize = pendingSize;

        // 释放消息( 数据 )相关的资源
        // release message and replace with an empty buffer
        ReferenceCountUtil.safeRelease(msg);
        // 设置为空 ByteBuf
        msg = Unpooled.EMPTY_BUFFER;

        // 置空属性
        pendingSize = 0;
        total = 0;
        progress = 0;
        bufs = null;
        buf = null;

        // 返回 pSize
        return pSize;
    }
    return 0;
}

addMessage(Object msg, int size, ChannelPromise promise) 方法,写入消息( 数据 )到内存队列。promise 只有在真正完成写入到对端操作,才会进行通知:

 /**
  * Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once
  * the message was written.
  */
 public void addMessage(Object msg, int size, ChannelPromise promise) {
     // 创建新 Entry 对象
     Entry entry = Entry.newInstance(msg, size, total(msg), promise);
     // 若 tailEntry 为空,将 flushedEntry 也设置为空。防御型编程,实际不会出现
     if (tailEntry == null) {
         flushedEntry = null;
     // 若 tailEntry 非空,将原 tailEntry 指向新 Entry
     } else {
         Entry tail = tailEntry;
         tail.next = entry;
     }
     // 更新 tailEntry 为新 Entry
     tailEntry = entry;
     // 若 unflushedEntry 为空,更新为新 Entry
     if (unflushedEntry == null) {
         unflushedEntry = entry;
     }
 
     // 增加 totalPendingSize 计数
     // increment pending bytes after adding message to the unflushed arrays.
     // See https://github.com/netty/netty/issues/1619
     incrementPendingOutboundBytes(entry.pendingSize, false);
 }

addFlush() 方法,标记内存队列每个 Entry 对象,开始 flush 。代码如下:

 public void addFlush() {
     // There is no need to process all entries if there was already a flush before and no new messages
     // where added in the meantime.
     //
     // See https://github.com/netty/netty/issues/2577
     Entry entry = unflushedEntry;
     if (entry != null) {
         // 若 flushedEntry 为空,赋值为 unflushedEntry ,用于记录第一个( 开始 ) flush 的 Entry 。
         if (flushedEntry == null) {
             // there is no flushedEntry yet, so start with the entry
             flushedEntry = entry;
         }
         // 计算 flush 的数量,并设置每个 Entry 对应的 Promise 不可取消
         do {
             // 增加 flushed
             flushed ++;
             // 设置 Promise 不可取消
             if (!entry.promise.setUncancellable()) { // 设置失败
                 // 减少 totalPending 计数
                 // Was cancelled so make sure we free up memory and notify about the freed bytes
                 int pending = entry.cancel();
                 decrementPendingOutboundBytes(pending, false, true);
             }
            // 获得下一个 Entry
             entry = entry.next;
         } while (entry != null);
 
         // 设置 unflushedEntry 为空,表示所有都 flush
         // All flushed so reset unflushedEntry
         unflushedEntry = null;
     }
 }

close(…) 方法,关闭 ChannelOutboundBuffer ,进行后续的处理:

void close(ClosedChannelException cause) {
    close(cause, false);
}

  void close(final Throwable cause, final boolean allowChannelOpen) {
      // 正在通知 flush 失败中
      if (inFail) {
          // 提交 EventLoop 的线程中,执行关闭
          channel.eventLoop().execute(new Runnable() {
              @Override
              public void run() {
                  close(cause, allowChannelOpen);
              }
          });
          // 返回
          return;
      }
 
      // 标记正在通知 flush 失败中
      inFail = true;
  
      if (!allowChannelOpen && channel.isOpen()) {
          throw new IllegalStateException("close() must be invoked after the channel is closed.");
      }
  
      if (!isEmpty()) {
          throw new IllegalStateException("close() must be invoked after all flushed writes are handled.");
      }
  
      // Release all unflushed messages.
      try {
          // 从 unflushedEntry 节点,开始向下遍历
          Entry e = unflushedEntry;
          while (e != null) {
              // 减少 totalPendingSize
              // Just decrease; do not trigger any events via decrementPendingOutboundBytes()
              int size = e.pendingSize;
              TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
  
              if (!e.cancelled) {
                  // 释放消息( 数据 )相关的资源
                  ReferenceCountUtil.safeRelease(e.msg);
                  // 通知 Promise 执行失败
                  safeFail(e.promise, cause);
              }
              // 回收当前节点,并获得下一个 Entry 节点
              e = e.recycleAndGetNext();
          }
      } finally {
          // 标记在在通知 flush 失败中
          inFail = false;
      }
  
      // 清除 NIO ByteBuff 数组的缓存。
      clearNioBuffers();
  }

ChannelOutboundBuffer 写入控制,当我们不断调用 addMessage(Object msg, int size, ChannelPromise promise) 方法,添加消息到 ChannelOutboundBuffer 内存队列中,如果不及时 flush 写到对端( 例如程序一直未调用 Channel中flush() 方法,或者对端接收数据比较慢导致 Channel 不可写 ),可能会导致 OOM 内存溢出。所以,在 ChannelOutboundBuffer 使用 totalPendingSize 属性,存储所有 Entry 预计占用的内存大小。

  1. 在 totalPendingSize 大于高水位阀值时( ChannelConfig.writeBufferHighWaterMark ,默认值为 64 KB ),关闭写开关( unwritable )
  2. 在 totalPendingSize 小于低水位阀值时( ChannelConfig.writeBufferLowWaterMark ,默认值为 32 KB ),打开写开关( unwritable )

思路在这,偷个懒,看了看就不写啦~

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

猜你喜欢

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