Netty系列10-客戶端源碼分析

客戶端的流程和服務端的大致相同,在服務端分析過的這裏不再分析,看這篇文章前可以先看下服務端源碼分析的文章。先大致看下客戶端的代碼。

 public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();/*線程組*/
        try {
            Bootstrap b = new Bootstrap();/*客戶端啓動必備*/
            b.group(group)
                .channel(NioSocketChannel.class)/*指明使用NIO進行網絡通訊*/
                .remoteAddress(new InetSocketAddress(host,port))/*配置遠程服務器的地址*/
                .handler(new EchoClientHandler());
            ChannelFuture f = b.connect().sync();/*連接到遠程節點,阻塞等待直到連接完成*/
            /*阻塞,直到channel關閉*/
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }

調用Bootstrap的connect方法之前的代碼,在之前服務端源碼分析的文章中已經介紹了,這裏不再重複,這裏直接從調用connect方法開始分析。

1. connect

    public ChannelFuture connect() {
    //參數校驗
        validate();
        SocketAddress remoteAddress = this.remoteAddress;
        if (remoteAddress == null) {
            throw new IllegalStateException("remoteAddress not set");
        }
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

這裏核心在doResolveAndConnect方法。

   private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    // Directly obtain the cause and do a null check so we only need one volatile read in case of a
                    // failure.
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

1.1 initAndRegister

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
        //服務端不同的是,客戶端這裏創建的是NioSocketChannel
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // channel can be null if newChannel crashed (eg SocketException("too many open files"))
                channel.unsafe().closeForcibly();
                // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

這個方法和服務器啓動的時候調用的是同一個方法,不過在細節上有所不同。

1.1.1 NioSocketChannel

服務端在這裏創建的是NioServerSocketChannel,客戶端這裏創建的是NioSocketChannel。看下構造方法。

 public NioSocketChannel() {
 //DEFAULT_SELECTOR_PROVIDER是JDK原生的SelectorProvider
        this(DEFAULT_SELECTOR_PROVIDER);
    }
    public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

newSocket利用SelectorProvider創建JDK的SocketChannel對象。

  public NioSocketChannel(Channel parent, SocketChannel socket) {
  //這裏的parent是null
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }

super調用父類AbstractNioByteChannel的構造方法

 protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, SelectionKey.OP_READ);
    }

繼續調用父類AbstractNioChannel的構造方法

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
//父類構造方法創建了NioByteUnsafe對象和該channel的Pipeline
        super(parent);
        this.ch = ch;
        //這裏的感興趣事件是read事件
        this.readInterestOp = readInterestOp;
        try {
        //設置通道非阻塞
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }

1.1.2 init

這裏調用的是Bootstrap的init方法。

void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        //將配置的handler加入Pipeline
        p.addLast(config.handler());
    //設置屬性
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }
        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }
        }
    }

1.1.3 register

register方法和服務端一樣調用的是MultithreadEventLoopGroup的register方法。

public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

next().register調用的是SingleThreadEventLoop的register方法。

 public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }
  public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

這裏將繼續調用AbstractChannel內部類AbstractUnsafe的register方法。

 public final void register(EventLoop eventLoop, final ChannelPromise promise) {
                         ...//省略代碼
            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                ...//省略代碼
                }
            }
        }

這個方法之前講過,就是分配一個eventLoop給channel,創建一個線程和eventLoop綁定,並且執行register0方法。

1.1.4 register0

  private void register0(ChannelPromise promise) {
            try {
                  ...//省略代碼
                boolean firstRegistration = neverRegistered;
                doRegister();
                neverRegistered = false;
                registered = true;

                    ...//省略代碼
                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                 ...//省略代碼
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                            ...//省略代碼
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                    ...//省略代碼
            }
        }

這個方法在之前也講過,不分析了。回到doResolveAndConnect方法繼續看接下來執行的doResolveAndConnect0方法。

1.2 doResolveAndConnect0

 private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            final EventLoop eventLoop = channel.eventLoop();
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);

            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
              //執行這裏的方法
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }

            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);

            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();

                if (resolveFailureCause != null) {
                    // Failed to resolve immediately
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    // Succeeded to resolve immediately; cached? (or did a blocking lookup)
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }

            // Wait until the name resolution is finished.
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        channel.close();
                        promise.setFailure(future.cause());
                    } else {
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        } catch (Throwable cause) {
            promise.tryFailure(cause);
        }
        return promise;
    }

這裏將執行doConnect方法。
(1)doConnect

 private static void doConnect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        final Channel channel = connectPromise.channel();
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (localAddress == null) {
                    channel.connect(remoteAddress, connectPromise);
                } else {
                    channel.connect(remoteAddress, localAddress, connectPromise);
                }
                connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        });
    }

這裏的channel.connect將繼續調用AbstractChannel的connect方法。

  public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return pipeline.connect(remoteAddress, promise);
    }

(2)connect

 public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, promise);
    }

connect是個出站事件,從TailContext開始出傳播。我們現在這裏有三個handler。HeadContext、EchoClientHandler(我們自定義的)、TailContext。
TailContext的connect方法是繼承其父類AbstractChannelHandlerContext的方法。

public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }
		//找到下一個出站事件去執行
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

這裏能處理出站事件的只有HeadContext。看下它的connect方法。

    public void connect(
                ChannelHandlerContext ctx,
                SocketAddress remoteAddress, SocketAddress localAddress,
                ChannelPromise promise) throws Exception {
            unsafe.connect(remoteAddress, localAddress, promise);
        }

這裏將繼續調用AbstractNioChannel內部類AbstractNioUnsafe的connect方法。
(3)AbstractNioUnsafe的connect

 public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            try {
                if (connectPromise != null) {
                    // Already a connect in process.
                    throw new ConnectionPendingException();
                }

                boolean wasActive = isActive();
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    ...//省略代碼}
            } catch (Throwable t) {
                promise.tryFailure(annotateConnectException(t, remoteAddress));
                closeIfClosed();
            }
        }

看下doConnect方法。
(4)doConnect

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
		//如果本地地址不爲空,綁定本地地址,我們這裏是空的
        if (localAddress != null) {
            doBind0(localAddress);
        }

        boolean success = false;
        try {
        //調用JDK原生API連接遠程主機
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            if (!connected) {
            //如果連接不成功,註冊CONNECT事件
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

到這裏客戶端啓動完畢。
如果這裏註冊了CONNECT事件,後面連接成功,觸發了CONNECT事件客戶端怎麼處理的。

2. connect事件

我們在上一篇文章中知道所有的處理事件都是由NioEventLoop的processSelectedKey方法處理。

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            final EventLoop eventLoop;
            try {
                eventLoop = ch.eventLoop();
            } catch (Throwable ignored) {
                ...//省略代碼
                return;
            }
             ...//省略代碼
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
             ...//省略代碼
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                //取消註冊connect事件
                k.interestOps(ops);

                unsafe.finishConnect();
            }
            
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

這裏首先取消了註冊的connect事件,然後調用了AbstractNioUnsafe的finishConnect方法。
(1)finishConnect

public final void finishConnect() {
            assert eventLoop().inEventLoop();

            try {
                boolean wasActive = isActive();
                doFinishConnect();
                fulfillConnectPromise(connectPromise, wasActive);
            } catch (Throwable t) {
                fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
            } finally {
                // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
                // See https://github.com/netty/netty/issues/1770
                if (connectTimeoutFuture != null) {
                    connectTimeoutFuture.cancel(false);
                }
                connectPromise = null;
            }
        }

(2)doFinishConnect

  protected void doFinishConnect() throws Exception {
        if (!javaChannel().finishConnect()) {
            throw new Error();
        }
    }

這裏只是做了下檢查
(3)fulfillConnectPromise

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
            if (promise == null) {
                // Closed via cancellation and the promise has been notified already.
                return;
            }

            boolean active = isActive();

            // trySuccess() will return false if a user cancelled the connection attempt.
            boolean promiseSet = promise.trySuccess();

            if (!wasActive && active) {
                pipeline().fireChannelActive();
            }

            // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
            if (!promiseSet) {
                close(voidPromise());
            }
        }

這裏最終調用了pipeline的fireChannelActive方法,這個方法在前一篇文章分析過,這裏不分析了,最終它會在channel上註冊read事件。
看到這裏服務端和客戶端的啓動源碼已經分析完了,accept、connect、read事件也分析過了,到這裏是不是有點疑惑,還有個write事件在哪。

3. write

3.1 ChannelHandlerContext的writeAndFlush

我們在netty中寫數據比較常用的是調用和handler綁定的ChannelHandlerContext的writeAndFlush方法。
這裏調用的是AbstractChannelHandlerContext的writeAndFlush方法。

  public ChannelFuture writeAndFlush(Object msg) {
        return writeAndFlush(msg, newPromise());
    }

public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
…//省略代碼

    write(msg, true, promise);

    return promise;
}

(1)write


    private void write(Object msg, boolean flush, ChannelPromise promise) {
    //這裏找能處理出站事件的handler,這裏是HeadContext
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
        //判斷是否需要flush
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

這裏先找下一個能處理出站事件的handler,這裏能處理出站事件的只有HeadContext。
(2) invokeWriteAndFlush
這裏會判斷是否需要flush,如果需要調用HeadContext的invokeWriteAndFlush,否則調用invokeWrite。

    private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }

    private void invokeWrite(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);
        } else {
            write(msg, promise);
        }
    }

這裏可以看出來flush的方法只比不需要多了invokeFlush0方法。
(3)invokeWrite0

   private void invokeWrite0(Object msg, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler()).write(this, msg, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    

這裏調用的是HeadContext的write方法。

 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            unsafe.write(msg, promise);
        }

接着調用AbstractUnsafe的write方法。
(4)write

public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop();

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
                // release message now to prevent resource-leak
                ReferenceCountUtil.release(msg);
                return;
            }

            int size;
            try {
                msg = filterOutboundMessage(msg);
                size = pipeline.estimatorHandle().size(msg);
                if (size < 0) {
                    size = 0;
                }
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                ReferenceCountUtil.release(msg);
                return;
            }

            outboundBuffer.addMessage(msg, size, promise);
        }

這個方法將數據寫入了outboundBuffer。
(4)invokeFlush0

  private void invokeFlush0() {
        try {
            ((ChannelOutboundHandler) handler()).flush(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    }

這裏最終調用的還是AbstractUnsafe的flush方法。
(5)flush

  public final void flush() {
            assertEventLoop();

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }

            outboundBuffer.addFlush();
            flush0();
        }

這裏調用了AbstractUnsafe的flush0方法。

 protected void flush0() {
          ...//省略代碼
            inFlush0 = true;
		 ...//省略代碼

            try {
                doWrite(outboundBuffer);
            } catch (Throwable t) {
                ...//省略代碼
                } else {
                    try {
                        shutdownOutput(voidPromise(), t);
                    } catch (Throwable t2) {
                        close(voidPromise(), t2, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
                    }
                }
            } finally {
                inFlush0 = false;
            }
        }

最終調用了NioSocketChannel的doWrite方法。
(6)doWrite

protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        SocketChannel ch = javaChannel();
        //最多循環寫入16次
        int writeSpinCount = config().getWriteSpinCount();
        do {
            if (in.isEmpty()) {
                // 如果沒有要寫的數據,清除write事件
                clearOpWrite();
                return;
            }

            // Ensure the pending writes are made of ByteBufs only.
            int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
            ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
            int nioBufferCnt = in.nioBufferCount();

            switch (nioBufferCnt) {
                case 0:
                    // We have something else beside ByteBuffers to write so fallback to normal writes.
                    //這裏是處理其他類型的數據寫入
                    writeSpinCount -= doWrite0(in);
                    break;
                case 1: {
               //nioBuffers數量爲1處理邏輯
                    ByteBuffer buffer = nioBuffers[0];
                    int attemptedBytes = buffer.remaining();
                    //寫數據
                    final int localWrittenBytes = ch.write(buffer);
                    if (localWrittenBytes <= 0) {
                        incompleteWrite(true);
                        return;
                    }
                    adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes);
                    --writeSpinCount;
                    break;
                }
                default: {
                   //nioBuffers數不爲1和0處理邏輯
                    long attemptedBytes = in.nioBufferSize();
                    final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                    if (localWrittenBytes <= 0) {
                    //如果沒有要寫的數據,註冊write事件
                        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);
		//writeSpinCount 大於0,代表在16次循環中數據已經寫完,否則代表沒寫完
        incompleteWrite(writeSpinCount < 0);
    }

nioBufferCnt爲0的處理邏輯,這個平時用的少不分析。nioBufferCnt爲1和不爲1的處理邏輯差不多,這裏以1爲例進行分析。
這個方法首先判斷是否有要寫入數據,沒有的話調用clearOpWrite方法清除write事件。

 protected final void clearOpWrite() {
        final SelectionKey key = selectionKey();
    
        if (!key.isValid()) {
            return;
        }
        final int interestOps = key.interestOps();
        if ((interestOps & SelectionKey.OP_WRITE) != 0) {
            key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
        }
    }

如果數據寫完後,調用incompleteWrite方法重新註冊write事件。

protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();
        } else {
          
            clearOpWrite();

            // Schedule flush again later so other tasks can be picked up in the meantime
            eventLoop().execute(flushTask);
        }
    }
 protected final void setOpWrite() {
        final SelectionKey key = selectionKey();
       
        if (!key.isValid()) {
            return;
        }
        final int interestOps = key.interestOps();
        if ((interestOps & SelectionKey.OP_WRITE) == 0) {
            key.interestOps(interestOps | SelectionKey.OP_WRITE);
        }
    }

如果有要寫入的數據,則循環向buffer中寫入數據,這裏最多循環寫入16次,最後調用incompleteWrite方法。
(7)incompleteWrite

protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        if (setOpWrite) {
            setOpWrite();
        } else {
           
            clearOpWrite();

            eventLoop().execute(flushTask);
        }
    }

如果在16次內已經把所有數據寫完,則重新註冊write事件,如果沒有寫完,先把write事件取消,再把flushTask加入線程等待隊列。
我們前面分析過,線程的等待隊列會在線程執行完所有在selector上註冊的感興趣事件後,會把等待隊列中的任務執行完。
看下這個任務是什麼

 private final Runnable flushTask = new Runnable() {
        @Override
        public void run() {
            // Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
            // meantime.
            ((AbstractNioUnsafe) unsafe()).flush0();
        }
    };

這個任務其實就是重新調用flush0寫數據。寫完後重新註冊write事件。

3.1 write事件

最後我們看下如果註冊了write事件,怎麼處理的。

 if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }

這裏最後調用的是AbstractNioUnsafe的forceFlush方法。

  public final void forceFlush() {
            // directly call super.flush0() to force a flush now
            super.flush0();
        }

這裏又回到調用AbstractUnsafe的flush0方法。上面已經分析過了。
到這裏所有accept、connect、read、write事件已經分析完畢了。這裏我們省略了不少過程,具體的可以在前一篇服務端的分析代碼中去看看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章