5、Netty那些事 - Channel源码解析

276 阅读13分钟

上篇文章主要为大家分析了NioEventLoop相关源码实现,其中提到在processSelectedKey方法中处理OP_WRITE、OP_READ、OP_ACCEPT、OP_CONNECT等事件,今天就让我们继续深入研究下Netty究竟是如何处理这些事件的。本篇文章内容较长,希望大家可以认真阅读,很多知识点都能和前面的文章形成闭环,比如分析ServerBootStrap提到过channel得初始化、NioEventLoop源码解析提到的重写execute方法和taskQueue等。

一、温故知新

    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
        try {
            int readyOps = k.readyOps();
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                ch.unsafe().forceFlush();
            }
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }
    }

以上代码调用路径为NioEventLoop-run -> processSelectedKeys -> processSelectedKeysOptimized -> processSelectedKey,不熟悉NioEventLoop原理的同学可以查看我上一篇文章。通过上述源码可以看到,最终调用的是AbstractNioChannel.NioUnsafe类来处理各种nio事件。

二、类图

image.png 老规矩,有图先看图,以上就是Netty Channel相关类图。Channel是Netty抽象出来对网络I/O进行读写的相关接口,主要功能有发起连接、处理连接、关闭连接等。分层很多,不同层负责不同的功能,特定的功能交给特定的子类去实现,那就让我们自上而下开始阅读Channel相关设计吧

三、AbstractChannel解读

查看其构造函数如下:

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

一共初始化了几个属性

  • parent由于channel有父子关系,这里记录下父信息
  • id,channel的唯一标识,有兴趣的同学可以研究下相关实现,由几部分组成,大致就是机器id+自增id+时间戳+随机数组成
  • unsafe类,实现具体的连接与读写,命名为unsafe表示不对外提供使用
  • pipeline,类型为DefaultChannelPipeline,handler容器,主要处理数据的编解码以及执行自定义的业务逻辑

3.1. newUnsafe方法

查看newUnsafe源码如下:

  protected abstract AbstractUnsafe newUnsafe();

方法很简单,抽象方法返回Unsafe类的实现类,不同的Channel实现类可以自定于自己的AbstractUnsafe,此处的AbstractUnsafe类则是AbstractChannel中提供的实现,大部分方法都是模版方法,具体的实现细节由子类完成,例如Bind方法:

        @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }
            boolean wasActive = isActive();
            try {
                // 抽象方法,由子类AbstractNioChannel去实现
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

其中调用的doBind则由子类NioServerSocketChannel来实现,还记得前文分析ServerBootStrap启动流程-bind方法分析吗?时序图里曾提到过,底层调用的就是NioServerSocketChannel的doBind方法,当时其实没太理解为什么这么设计,觉得冗余封装,这次自顶向下看才理解,其实每层都有每层的职责。

3.2. EventLoop属性

每一个channel都会对应一个EventLoop线程,EventLoop属性不是在初始化channel的时候创建的,而且调用register方法时候,传递进来的,查看AbstractUnsafe-register方法源码如下:

        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "eventLoop");
            .. ...
            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

可以看到,在register方法中,通过eventLoop线程执行register0方法,从而注册channel。这里的execute方法,还记得前文分析NioEventLoop时候,提到过其父类重写了execute方法,此处调用就是其父类SingleThreadEventExecutor的execute方法,把任务放到taskQueue中,NioEventLoop线程启动后,会轮询taskQueue,通过runAllTask取出对应的任务,执行对应任务的run方法,对应到这里就是执行register0方法。通过debug也可以验证我们的想法

image.png 至于register方法的调用链路,还记得分析ServerBootStrap启动流程时候,提到了dobind的时候,首先执行initAndRegister方法,底层调用的就是AbstractChannel.AbstractUnsafe-register方法。

四、AbstractNioChannel解读

AbstractNioChannel本身也是一个抽象类,在AbstractChannel基础上增加了一些属性和方法,查看其构造函数如下:

    private final SelectableChannel ch;
    protected final int readInterestOp;
    protected volatile SelectionKey selectionKey;

    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            ch.close();

        }
    }

主要属性如下:

  • SelectableChannel ch,jdk原生nio对象
  • int readInterestOp,nio事件类型
  • SelectionKey selectionKey,注册selector后获取的key

可以看到,在AbstractNioChannel类中,已经将Netty的channel和jdk原生nio类关联起来了。

4.1. doRegister方法

分析bstractChannel.AbstractUnsafe-register时提到过,doRegister最终交给子类AbstractNioChannel去实现。查看源码如下:

    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                ... ...
            }
        }
    }

功能如下:

  • 通过javaChannel()方法获取具体的nio channel,也就是构造函数中初始化的ch对象
  • eventLoop().unwrappedSelector()获取到的是NioEventLoop的里unwrappedSelector对象,也就是jdk原生的selecotr对象
  • javaChannel().register()方法表示把ch对象注册到NioEventLoop的selecotr上
  • 注册成功后返回selectionKey,为其设置感兴趣的事件

4.2. AbstractNioUnsafe解读

在AbstractNioChannel类中,也对应存在一个抽象类AbstractNioUnsafe,继承了AbstractUnsafe类,实现了NioUnsafe接口。较为重要的是connect方法,源码如下:

        @Override
        public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            try {
                ...省略校验 ... 
                boolean wasActive = isActive();
                // 协议不同,连接方式也不同,因此doConnect方法由子类去实现
                if (doConnect(remoteAddress, localAddress)) {
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // 获取连接超时事件
                    int connectTimeoutMillis = config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        // 设置定时任务,最终还是由NioEventLoop线程去执行
                        connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                                if (connectPromise != null && !connectPromise.isDone()
                                        && connectPromise.tryFailure(new ConnectTimeoutException(
                                                "connection timed out: " + remoteAddress))) {
                                    close(voidPromise());
                                }
                            }
                        }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
                    }
                    // 结果监听
                    promise.addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.isCancelled()) {
                                if (connectTimeoutFuture != null) {
                                    connectTimeoutFuture.cancel(false);
                                }
                                connectPromise = null;
                                close(voidPromise());
                            }
                        }
                    });
                }
            } catch (Throwable t) {
                promise.tryFailure(annotateConnectException(t, remoteAddress));
                closeIfClosed();
            }
        }

connect方法,较为重要的逻辑链接逻辑交给了子类去实现,毕竟不同的协议,处理连接的方式可能不同。此外还有一个方法值得关注下,那就是finishConnect,源码如下

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

            try {
                boolean wasActive = isActive();
                // 判断连接结果,子类完成
                doFinishConnect();
                // 将SocketChannel改为监听读操作
                fulfillConnectPromise(connectPromise, wasActive);
            } catch (Throwable t) {
                // 异常处理
                fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
            } finally {
                if (connectTimeoutFuture != null) {
                    connectTimeoutFuture.cancel(false);
                }
                connectPromise = null;
            }
        }

此方法只能在NioEventLoop中processSelectedKey方法中被调用,前文也层提到过。

五、AbstractNioByteChannel解读

AbstractNioChannel内部包装了nio channel,具备nio的注册和连接等功能,但是I/O的读写交给了子类去实现。通过类图也可以看出,AbstractNioChannel的实现有AbstractNioByteChannel和AbstractNioMessageChannel,前者发送和读取的对象是ByteBuf和 FileRegion,后者读写的对象是POJO对象。下面就让我们先来看看AbstractNioByteChannel相关实现。

5.1. flushTask属性

属性flushTask为task任务,定义如下:

   private final Runnable flushTask = new Runnable() {
        @Override
        public void run() {
            ((AbstractNioUnsafe) unsafe()).flush0();
        }
    };

主要负责刷新发送缓存链表中的数据,write的数据没直接写到socket里,而是卸载了ChannelOutboundBuffer缓存中,当调用unsafe()).flush0()时候,最终调用的也是AbstractChannel里的抽象方法doWrite,具体的写数据逻辑,也交给了子类去实现

5.2. doWrite方法

主要功能是获取ChannelOutboundBuffer缓存中待发送的数据,进行循环发送,查看方法源码如下:

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        // 写请求自旋次数,默认为16
        int writeSpinCount = config().getWriteSpinCount();
        do {
            // 获取当前channel得缓存ChannelOutboundBuffer中,还未发出去的消息
            Object msg = in.current();
            if (msg == null) {
                // 所有消息都发成功了,清除cnannel中OP_WRITE事件
                clearOpWrite();
                return;
            }
            // 发送数据
            writeSpinCount -= doWriteInternal(in, msg);
        } while (writeSpinCount > 0);
        
        // 缓冲区满了导致发送失败,doWriteInternal返回Integer.MAX_VALUE,此时writeSpinCount=Integer.MAX_VALUE,此时writeSpinCount < 0 = true
        // 当发送16次还未发送完全,但每次都写成功时候,此时writeSpinCount=0,riteSpinCount < 0 = false
        incompleteWrite(writeSpinCount < 0);
    }

查看incompleteWrite源码如下:

    protected final void incompleteWrite(boolean setOpWrite) {
        if (setOpWrite) {
            setOpWrite();
        } else {
            eventLoop().execute(flushTask);
        }
    }

setOpWrite=true,也就是上面分析的,此时缓冲区已经满了,导致发送失败,将OP_WRITE写操作时间添加到Channel的选择Key兴趣事件集中;

setOpWrite=false,表示写了16次还没写完,把选择Key的OP_WRITE事件从兴趣事件中移除,添加一个flushTask,并交给NioEventLoop线程去执行,NioEventLoop run方法自旋轮训到次任务时再执行。

查看doWriteInternal源码如下:

    private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
        // 处理ByteBuf类型数据
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (!buf.isReadable()) {
                // 
                in.remove();
                return 0;
            }
            // 实际发送的字节数据,交给子类去实现
            final int localFlushedAmount = doWriteBytes(buf);
            if (localFlushedAmount > 0) {
                in.progress(localFlushedAmount);
                if (!buf.isReadable()) {
                    in.remove();
                }
                // 正常发送,返回1,从16次里-1
                return 1;
            }
        } else if (msg instanceof FileRegion) { // 处理 FileRegion类型数据
            FileRegion region = (FileRegion) msg;
            if (region.transferred() >= region.count()) {
                in.remove();
                return 0;
            }
            // 实际发送的字节数据,子类实现
            long localFlushedAmount = doWriteFileRegion(region);
            if (localFlushedAmount > 0) {
                in.progress(localFlushedAmount);
                if (region.transferred() >= region.count()) {
                    in.remove();
                }
                return 1;
            }
        } else {
            // Should not reach here.
            throw new Error();
        }
        // 发送失败,返回Integer.MAX_VALUE
        return WRITE_STATUS_SNDBUF_FULL;
    }

doWrite方法如上述所示,核心的doWriteBytes方法留给了子类去实现。

5.3. NioByteUnsafe解读

和其父类一样,AbstractNioByteChannel内部也有一个Unsafe类,用来执行I/O相关的操作,只不过这一次,不再是抽象类,而是继承了AbstractNioUnsafe类,实现了最后的抽象方法read,定义如下:

 protected class NioByteUnsafe extends AbstractNioUnsafe {
          @Override
        public final void read() {
            // 获取配置
            final ChannelConfig config = config();
            // 通道关闭
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();
            // 内存分配器,默认实现为PooledByteBufAllocator
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            // 清空上一次读取的字节数
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    // 通过allocator分配内存
                    byteBuf = allocHandle.allocate(allocator);
                    // 读取通道接受缓冲区数据
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // 没有数据可读,释放内存
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            readPending = false;
                        }
                        break;
                    }
                    // 更新读取消息计数器
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    // tcp传输会产生粘包问题,因此每次读都要触发channelRed事件,进而可以调用业务处理handler或者自定义拆包handler等
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
                // 操作完毕
                allocHandle.readComplete();
                // 触发channel通道的readComplete事件
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // See https://github.com/netty/netty/issues/2254
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
 }

5.4. 小节

通过上述分析可以看到,AbstractNioByteChannel内部类NioByteUnsafe实现了read方法,通过ByteBufAllocator进行缓冲区的读取。同时AbstractNioByteChannel实现了write方法,定义了用哪种方式刷新缓冲区的数据,只不过核心逻辑doWriteBytes方法依然留给了子类去实现。

六、AbstractNioMessageChannel解读

AbstractNioMessageChannel类写入和读取的数据都是Object,而不是字节流,因此,读数据的时候不存在粘包问题,所以可以先循环读完再触发channelRead事件。写逻辑就相对简单一些,把缓存在outBoundBuffer中的数据依次写入channel,直到数据全部写完,或者写次数不足再停止。

6.1. doWrite方法

doWrite方法如下:

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        final SelectionKey key = selectionKey();
        final int interestOps = key.interestOps();

        int maxMessagesPerWrite = maxMessagesPerWrite();
        // 一直写,直到次数不足,次数可配置,默认是Integer.MAX_VALUE
        while (maxMessagesPerWrite > 0) {
            Object msg = in.current();
            if (msg == null) {
                break;
            }
            try {
                boolean done = false;
                // 获取配置中循环写的最大次数
                for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
                    // 调用子类去写数据
                    if (doWriteMessage(msg, in)) {
                        done = true;
                        break;
                    }
                }

                if (done) {
                    // 写成功,则从缓存链中移除,继续发送下一个节点
                    maxMessagesPerWrite--;
                    in.remove();
                } else {
                    break;
                }
            } catch (Exception e) {
                if (continueOnWriteError()) {
                    maxMessagesPerWrite--;
                    in.remove(e);
                } else {
                    throw e;
                }
            }
        }
        if (in.isEmpty()) {
            if ((interestOps & SelectionKey.OP_WRITE) != 0) {
                key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
            }
        } else {
            if ((interestOps & SelectionKey.OP_WRITE) == 0) {
                key.interestOps(interestOps | SelectionKey.OP_WRITE);
            }
        }
    }

6.2. read方法

read方法源码如下:

        @Override
        public void read() {
            // 这里其实没看太懂为啥加这个判断
            assert eventLoop().inEventLoop();
            ...逻辑类似 ...
            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        // 读取数据,子类去实现
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }
                        // 记录读取的次数
                        allocHandle.incMessagesRead(localRead);
                    } while (continueReading(allocHandle)); // 默认不超过16次
                } catch (Throwable t) {
                    exception = t;
                }
                // 循环处理读到的数据包
                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // 每个数据包都触发channelRead
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                // 触发readComplete事件
                pipeline.fireChannelReadComplete();
                ... ... 
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

七、NioSocketChannel解读

通过类图可以看到,AbstractNioByteChannel的实现类为NioSocketChannel,通过方法名也可以看出来,此类不在是抽象类,实现了最后遗留的抽象方法,同时也实现了io.netty.channel.socket.ServerSocketChannel类,其实再Netty服务中,每一个socket连接都会对应生成一个NioSocketChannel对象,该类主要负责I/O具体的读写和连接操作。核心方法如下:

  • doReadBytes方法
    • 负责从socket中读取数据,在AbstractNioByteChannel.NioByteUnsafe-read方法中被调用
    @Override
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        // 获取计算内存分配器handle
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        // 设置尝试读取字节数为buf的可写字节数
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());
        // 从channel中读取字节并写入buf中,返回本次读取的字节数
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }

  • doConnect方法
    • 负责和客户端连接,在AbstractNioChannel.AbstractNioUnsafe-connect方法中被调用
    @Override
    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
            // 通过channel进行网络连接
            boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
            if (!connected) {
                // 连接未成功,则绑定OP_CONNECT事件
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
    }
  • doWriteBytes
    • 负责将ByteBuf类型数据写入socket,在AbstractNioByteChannel-doWriteInternal方法中被调用
    @Override
    protected int doWriteBytes(ByteBuf buf) throws Exception {
        // 获取buf的可读字节书
        final int expectedWrittenBytes = buf.readableBytes();
        // 将buf写入socket中,返回写入的字节书
        return buf.readBytes(javaChannel(), expectedWrittenBytes);
    }
  • doWriteFileRegion
    • 负责将FileRegion类型数据写入socket,在AbstractNioByteChannel-doWriteInternal方法中被调用
    @Override
    protected long doWriteFileRegion(FileRegion region) throws Exception {
        final long position = region.transferred();
        // 基于文件内存映射技术进行文件发送
        return region.transferTo(javaChannel(), position);
    }
  • doWrite方法
    • 负责将数据写入socket中,在AbstractChannel.AbstractUnsafe-flush0方法中被调用

以上方法,其实在前面的几个小节中也都提到过,不同的子类有不同的实现方式。大部分逻辑都很简单,就不展开讲解了重点讲解下doWrite方法。

7.1. doWrite方法

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        SocketChannel ch = javaChannel();
        // buf的可读字节数
        int writeSpinCount = config().getWriteSpinCount();
        do {
            // 无数据可写
            if (in.isEmpty()) {
                clearOpWrite();
                return;
            }

            // 获取一次最大可写字节数
            int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
            // 缓存由多个entry组成,每次写时都可能写多个entry
            ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
            int nioBufferCnt = in.nioBufferCount();
            // 缓存汇总buffer的数量
            switch (nioBufferCnt) {
                case 0:
                    // 非byteBuffer数据,父去处理
                    writeSpinCount -= doWrite0(in);
                    break;
                case 1: {
                    // 可读buffer
                    ByteBuffer buffer = nioBuffers[0];
                    // buffer可读字节数
                    int attemptedBytes = buffer.remaining();
                    // 讲buffer发送到socket缓存中
                    final int localWrittenBytes = ch.write(buffer);
                    // 发送失败
                    if (localWrittenBytes <= 0) {
                        // 讲写事件添加到事件兴趣中
                        incompleteWrite(true);
                        return;
                    }
                    // 根据成功写入字节数和尝试写入字节数调整下次最大可写字节数
                    adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    // 写多少,移除多少
                    in.removeBytes(localWrittenBytes);
                    // 循环次数减1
                    --writeSpinCount;
                    break;
                }
                default: {
                    // 尝试写入字节数
                    long attemptedBytes = in.nioBufferSize();
                    // 发送到socket缓存中的字节数
                    final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                    // 发送失败
                    if (localWrittenBytes <= 0) {
                        incompleteWrite(true);
                        return;
                    }
                    // 动态调整
                    adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
                            maxBytesPerGatheringWrite);
                    in.removeBytes(localWrittenBytes);
                    --writeSpinCount;
                    break;
                }
            }
        } while (writeSpinCount > 0);
        
        // 通过次数为0判断是否发送完全
        incompleteWrite(writeSpinCount < 0);
    }

八、NioServerSocketChannel解读

NioServerSocketChannel为AbstractMessageByteChannel的子类,专供server端使用,只负责监听socket接入,不负责I/O读写。重点关注下如何建立链接即可

    @Override
    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = SocketUtils.accept(javaChannel());

        try {
            if (ch != null) {
                // 每个新的链接都会构建一个 NioSocketChannel
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }
        } catch (Throwable t) {
            try {
                // 异常处理
                ch.close();
            } catch (Throwable t2) {
                logger.warn("Failed to close a socket.", t2);
            }
        }

        return 0;
    }

九、小节

本文主要为大家分析了下Netty channel相关实现。Netty的源码封装的确实有些复杂,不过按照类图一路梳理下来也有种豁然开朗的感觉,和前文的ServerBoot启动,以及NioEventLoop等知识点也都串联起来了,内容有些多,大家可以先收藏,后面有时间慢慢看哈。