关闭

netty4.0.x源码分析—write和flush

390人阅读 评论(0) 收藏 举报
分类:

       一般的开发者write和flush数据,都是基于ChannelHandlerContext ctx,然后调用其相应的write和flush方法。下面分别对这两个方法进行代码分析。(这里顺便提示一下,这两个方法都是从应用往底层发数据,属于OutboundHandler类型。如果没有特别的需求,我们不需要定义自己的handler,可以使用默认的Handler,这个在后面的分析中会体现。)

1、write方法

下面是ChannelHandlerContext的具体类DefaultChannelHandlerContext中write的相关代码

  1. @Override  
  2. public ChannelFuture write(Object msg) {  
  3.     return write(msg, newPromise()); //构造了一个newPromise对象,调用带promise的write方法,  
  4. }  
  5.   
  6. @Override  
  7. public ChannelFuture write(final Object msg, final ChannelPromise promise) {  
  8.     if (msg == null) {  
  9.         throw new NullPointerException("msg");  
  10.     }  
  11.   
  12.     validatePromise(promise, true);//验证promise  
  13.   
  14.     write(msg, false, promise);//再一次调用write方法,flush参数设置为false,这个参数比较重要,真正的数据发送过程是flush,后面会提到  
  15.   
  16.     return promise;  
  17. }  
  18.   
  19. private void write(Object msg, boolean flush, ChannelPromise promise) {  
  20.   
  21.     DefaultChannelHandlerContext next = findContextOutbound();  
  22.     EventExecutor executor = next.executor();  
  23.     if (executor.inEventLoop()) {  
  24.         next.invokeWrite(msg, promise);//调用invokeWrite函数  
  25.         if (flush) {  
  26.             next.invokeFlush();  
  27.         }  
  28.     } else {  
  29.         int size = channel.estimatorHandle().size(msg);  
  30.         if (size > 0) {  
  31.             ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer();  
  32.             // Check for null as it may be set to null if the channel is closed already  
  33.             if (buffer != null) {  
  34.                 buffer.incrementPendingOutboundBytes(size, false);  
  35.             }  
  36.         }  
  37.         executor.execute(WriteTask.newInstance(next, msg, size, flush, promise));  
  38.     }  
  39. }  
  40.   
  41. pre name="code" class="java">    private void invokeWrite(Object msg, ChannelPromise promise) {  
  42.     try {  
  43.         ((ChannelOutboundHandler) handler).write(this, msg, promise);//调用handler的write方法,这里我们使用HeadHandler默认的Handler方法。  
  44.     } catch (Throwable t) {  
  45.         notifyOutboundHandlerException(t, promise);  
  46.     }  
  47. }  
下面再看HeadHandler类的write方法
  1. @Override  
  2. public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {  
  3.     unsafe.write(msg, promise);  
  4. }  
还记得之前,我们提到的unsafe对象吗,从这个代码中,我们可以看出unsafe对象的重要性了,真正的“苦力”还是unsafe对象啊,所以不容忽视它,赶紧来看看它的实现,下面是unsafe的write方法,主要unsafe是一个接口,下面又多个扩展的具体类,这里的write方法在protected abstract class AbstractUnsafe中定义,注意它是AbstractChannel的一个内部类。下面就是write的定义。
  1. @Override  
  2. public void write(Object msg, ChannelPromise promise) {  
  3.     if (!isActive()) {  
  4.         // Mark the write request as failure if the channel is inactive.  
  5.         if (isOpen()) {  
  6.             promise.tryFailure(NOT_YET_CONNECTED_EXCEPTION);  
  7.         } else {  
  8.             promise.tryFailure(CLOSED_CHANNEL_EXCEPTION);  
  9.         }  
  10.         // release message now to prevent resource-leak  
  11.         ReferenceCountUtil.release(msg);  
  12.     } else {  
  13.         outboundBuffer.addMessage(msg, promise);  
  14.     }  
  15. }  
通过这个代码,我们可以清楚的看到,其实write的过程,只是把数据写入了一个buffer中,并没有真正的发送。真正的发送过程是我们后面要分析的flush的过程,好吧赶紧看看flush的过程。

2、flush方法

上面讲到flush是真正的发送数据的过程,所以这个方法还是比较关键的。先来看看DefaultChannelHandlerContext中的相关代码:

  1. @Override  
  2. public ChannelHandlerContext flush() {  
  3.     final DefaultChannelHandlerContext next = findContextOutbound();  
  4.     EventExecutor executor = next.executor();  
  5.     if (executor.inEventLoop()) {  
  6.         next.invokeFlush();  
  7.     } else {  
  8.         Runnable task = next.invokeFlushTask;  
  9.         if (task == null) {  
  10.             next.invokeFlushTask = task = new Runnable() {  
  11.                 @Override  
  12.                 public void run() {  
  13.                     next.invokeFlush();  
  14.                 }  
  15.             };  
  16.         }  
  17.         executor.execute(task);  
  18.     }  
  19.   
  20.     return this;  
  21. }  
  22.   
  23. private void invokeFlush() {  
  24.     try {  
  25.         ((ChannelOutboundHandler) handler).flush(this);  
  26.     } catch (Throwable t) {  
  27.         notifyHandlerException(t);  
  28.     }  
  29. }  
这个代码逻辑还是比较清晰的,还是我们之前分析的最终调用默认的HeadHandler来处理flush,下面是HeadHandler的flush定义。
  1. @Override  
  2. public void flush(ChannelHandlerContext ctx) throws Exception {  
  3.     unsafe.flush();  
  4. }  
这里又是unsafe这个苦力,在做事情。那就看看unsafe中flush的定义吧,仍然是在protected abstract class AbstractUnsafe中定义的,注意它是AbstractChannel的一个内部类。
  1. @Override  
  2. public void flush() {  
  3.     ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;  
  4.     if (outboundBuffer == null) {  
  5.         return;  
  6.     }  
  7.   
  8.     outboundBuffer.addFlush();  
  9.     flush0();  
  10. }  
  11.   
  12. protected void flush0() {  
  13.     if (inFlush0) {  
  14.         // Avoid re-entrance  
  15.         return;  
  16.     }  
  17.   
  18.     final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;  
  19.     if (outboundBuffer == null || outboundBuffer.isEmpty()) {  
  20.         return;  
  21.     }  
  22.   
  23.     inFlush0 = true;  
  24.   
  25.     // Mark all pending write requests as failure if the channel is inactive.  
  26.     if (!isActive()) {  
  27.         try {  
  28.             if (isOpen()) {  
  29.                 outboundBuffer.failFlushed(NOT_YET_CONNECTED_EXCEPTION);  
  30.             } else {  
  31.                 outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION);  
  32.             }  
  33.         } finally {  
  34.             inFlush0 = false;  
  35.         }  
  36.         return;  
  37.     }  
  38.   
  39.     try {  
  40.         doWrite(outboundBuffer);  
  41.     } catch (Throwable t) {  
  42.         outboundBuffer.failFlushed(t);  
  43.         if (t instanceof IOException) {  
  44.             close(voidPromise());  
  45.         }  
  46.     } finally {  
  47.         inFlush0 = false;  
  48.     }  
  49. }  
逻辑仍然很简单,我们看到这里它最终是调用了doWrite函数,而且以buffer作参数,这个buffer其实就是write时数据存储的地方。write的过程这是就要和Channel打交道了,还记得之前我们分析过的channel吗,这里write定义在AbstractNioByteChannel类中(Server端以字节的形式发送数据),下面是具体的代码:
  1. @Override  
  2. protected void doWrite(ChannelOutboundBuffer in) throws Exception {  
  3.     int writeSpinCount = -1;  
  4.   
  5.     for (;;) {  
  6.         Object msg = in.current();  
  7.         if (msg == null) {  
  8.             // Wrote all messages.  
  9.             clearOpWrite();  
  10.             break;  
  11.         }  
  12.   
  13.         if (msg instanceof ByteBuf) {  
  14.             ByteBuf buf = (ByteBuf) msg;  
  15.             int readableBytes = buf.readableBytes();  
  16.             if (readableBytes == 0) {  
  17.                 in.remove();  
  18.                 continue;  
  19.             }  
  20.             if (!buf.isDirect()) {  
  21.                 ByteBufAllocator alloc = alloc();  
  22.                 if (alloc.isDirectBufferPooled()) {  
  23.                     // Non-direct buffers are copied into JDK's own internal direct buffer on every I/O.  
  24.                     // We can do a better job by using our pooled allocator. If the current allocator does not  
  25.                     // pool a direct buffer, we rely on JDK's direct buffer pool.  
  26.                     buf = alloc.directBuffer(readableBytes).writeBytes(buf);  
  27.                     in.current(buf);  
  28.                 }  
  29.             }  
  30.             boolean done = false;  
  31.             long flushedAmount = 0;  
  32.             if (writeSpinCount == -1) {  
  33.                 writeSpinCount = config().getWriteSpinCount();  
  34.             }  
  35.             for (int i = writeSpinCount - 1; i >= 0; i --) {  
  36.                 int localFlushedAmount = doWriteBytes(buf);  
  37.                 if (localFlushedAmount == 0) {  
  38.                     break;  
  39.                 }  
  40.   
  41.                 flushedAmount += localFlushedAmount;  
  42.                 if (!buf.isReadable()) {  
  43.                     done = true;  
  44.                     break;  
  45.                 }  
  46.             }  
  47.   
  48.             in.progress(flushedAmount);  
  49.   
  50.             if (done) {  
  51.                 in.remove();  
  52.             } else {  
  53.                 // Did not write completely.  
  54.                 setOpWrite();  
  55.                 break;  
  56.             }  
  57.         } else if (msg instanceof FileRegion) {  
  58.             FileRegion region = (FileRegion) msg;  
  59.             boolean done = false;  
  60.             long flushedAmount = 0;  
  61.             if (writeSpinCount == -1) {  
  62.                 writeSpinCount = config().getWriteSpinCount();  
  63.             }  
  64.             for (int i = writeSpinCount - 1; i >= 0; i --) {  
  65.                 long localFlushedAmount = doWriteFileRegion(region);  
  66.                 if (localFlushedAmount == 0) {  
  67.                     break;  
  68.                 }  
  69.   
  70.                 flushedAmount += localFlushedAmount;  
  71.                 if (region.transfered() >= region.count()) {  
  72.                     done = true;  
  73.                     break;  
  74.                 }  
  75.             }  
  76.   
  77.             in.progress(flushedAmount);  
  78.   
  79.             if (done) {  
  80.                 in.remove();  
  81.             } else {  
  82.                 // Did not write completely.  
  83.                 setOpWrite();  
  84.                 break;  
  85.             }  
  86.         } else {  
  87.             throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg));  
  88.         }  
  89.     }  
  90. }  
从上面的代码逻辑,我们看到了最终write可能调用两个函数,一个是doWriteBytes,一个是doWriteFileRegion,这里先介绍doWriteBytes,后面有时间再介绍doWriteFileRegion。好了,我们继续看doWriteBytes这个函数的实现。它被定义在NioSocketChannel中,这是我们用来处理客户端连接常用的Channel。
  1. @Override  
  2. protected int doWriteBytes(ByteBuf buf) throws Exception {  
  3.     final int expectedWrittenBytes = buf.readableBytes();  
  4.     final int writtenBytes = buf.readBytes(javaChannel(), expectedWrittenBytes);  
  5.     return writtenBytes;  
  6. }  
上面的代码最终调用了buf.readBytes(channel, bytes)函数,其实这里已经开始调用真正的JDK的Socket Channel开始发送数据了。我们还是来简单的看看这个函数,后面有时间再详细介绍ByteBuf整个结构。直接上代码,这两个函数分别定义在AbstractByteBuf类和ReadOnlyByteBufferBuf中,可以看出最终就是通过JDK的channel.write函数进行数据发送。
  1.  @Override  
  2.  public int readBytes(GatheringByteChannel out, int length)  
  3.          throws IOException {  
  4.      checkReadableBytes(length);  
  5.      int readBytes = getBytes(readerIndex, out, length);  
  6.      readerIndex += readBytes;  
  7.      return readBytes;  
  8.  }  
  9.   
  10.  @Override  
  11.  public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {  
  12.      ensureAccessible();  
  13.      if (length == 0) {  
  14.          return 0;  
  15.      }  
  16.   
  17.      ByteBuffer tmpBuf = internalNioBuffer();  
  18.      tmpBuf.clear().position(index).limit(index + length);  
  19.      return out.write(tmpBuf);  
  20.  }  

3、总结

本文简单的分析了write和flush的过程,从上到下的理了一遍数据write和flush的完整过程。另外在最后发送数据的时候,我们提到了ByteBuf,这个在后面我会写一篇文章来详细介绍。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1402340次
    • 积分:21630
    • 等级:
    • 排名:第333名
    • 原创:470篇
    • 转载:1953篇
    • 译文:3篇
    • 评论:69条
    文章分类
    最新评论
    博客推荐
    http://www.vpser.net/