【Netty专题】Netty源码剖析_Channel通道

首先我们来思考一个问题,什么是Channel?

Channel是Netty抽象出来的对网络I/O进行读/写的相关接口,与NIO中的Channel接口类似。

那么Channel有哪些主要功能?

  • 网络I/O的读/写
  • 客户端发起连接
  • 主动关闭连接、关闭链路
  • 获取通信双方的地址

说明:

Netty支持除了TCP以外的多种协议。不同协议、不同阻塞类型的连接会有所不同的Channel类型与之对应。

下面我们来看一下常见的几种Channel:

1. AbstractChannel

  • 首先他有几个主要属性:

EventLoop:每个Channel对应一条EventLoop线程。
DefaultChannelPipeline:一个Handler的容器,也可以将其理解为一个Handler链。Handler主要处理数据的编/解码和业务逻辑。
Unsafe:实现具体的连接与读/写数据,如网络的读/写、链路关闭、发起连接等。命名为Unsafe表示不对外提供使用,并非不安全。

我们来看一下AbstractChannel的功能图:
在这里插入图片描述
接下来我们来看源码:

  • 先看一些全局变量:
 private final Channel parent;
    private final ChannelId id;
    // 实现具体的连接读/写数据,如网络的读/写、链路关闭、发起连接等,unsafe表示不对外提供使用,并非不安全
    private final Unsafe unsafe;
    // handler调用链
    private final DefaultChannelPipeline pipeline;
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);

    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    // 每个channel对应一个eventloop线程
    private volatile EventLoop eventLoop;
    private volatile boolean registered;
    private boolean closeInitiated;
    private Throwable initialCloseCause;
  • AbstractChannel中有一个子类AbstractUnsafe,这个类大量采用了“模板设计模式”,具体的实现由子类完成,例如其中的bind()方法:
@Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();

            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
                !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can't receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
                // 模板设计模式,调用子类NioServerSocketChannel的doBind()方法
                doBind(localAddress);
            } catch (Throwable t) {
                // 绑定失败回调
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }

            // 从非活跃状态到活跃状态触发了active事件
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            // 绑定成功回调通知
            safeSetSuccess(promise);
        }

2. AbstractNioChannel

AbstractNioChannel 继承了 AbstractChannel,不过在其基础上又增加了一些属性和方法。

// 真正用到的NIO channel
    private final SelectableChannel ch;
    // 监听感兴趣的事件
    protected final int readInterestOp;
    // 注册到Selector后获取Key
    volatile SelectionKey selectionKey;

我们来看其中的一个主要方法,doRegister():

 /**
     * 在AbstractUnsafe的register0()方法中调用
     * @throws Exception
     */
    @Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                /**
                 * 通过javaChannel()方法获取具体的Nio Channel
                 * 把Channel注册到其EventLoop线程的Selector上
                 * 对于注册后返回的selectionKey,需要为其设置Channel感兴趣的事件
                 */
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    // 由于尚未调用select.select(...)
                    // 因此可能仍在缓存而未删除但已取消selectionKey
                    // 强制调用 selector.selectNow方法
                    // 将已经取消的selectionKey 从Selector上删除
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // 只有第一次跑出此异常, 才能调用selector.selectorNow进行取消
                    // 如果调用selector.selectNow 还有取消的缓存,则可能是JDK的一个bug
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

在AbstractNioChannel中有个非常重要的类——AbstractNioUnsafe,它继承了AbstractUnsafe,并且实现了其中的connect,flush0()等方法。
我们来探究一下connect方法:

@Override
        public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            // 设置任务为不可取消状态,并确定channel已打开
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }

            try {
                // 确保没有正在进行的连接
                if (connectPromise != null) {
                    // Already a connect in process.
                    throw new ConnectionPendingException();
                }
                // 获取之前的状态
                boolean wasActive = isActive();
                /**
                 * 在远程连接时,会出现以下三种结果。
                 * 1. 连接成功,返回true
                 * 2. 暂时没有连接上,服务端没有返回ACK应答,连接结果不确定,返回false
                 * 3. 连接失败,直接抛出I/O异常
                 * 由于协议和I/O模型不同,连接的方式也不一样,因此具体实现由子类实现
                 */
                if (doConnect(remoteAddress, localAddress)) {
                    /**
                     * 连接成功后会触发ChannelActive事件
                     * 最终会将NioSocketChannel中的selectionKey
                     * 设置为SelectionKey.OP_READ
                     * 用于监听网络读操作位
                     */
                    fulfillConnectPromise(promise, wasActive);
                } else {
                    connectPromise = promise;
                    requestedRemoteAddress = remoteAddress;

                    // Schedule connect timeout.
                    // 获取连接超时事件
                    int connectTimeoutMillis = config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        // 根据连接超时事件设置定时任务
                        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();
            }
        }

其中finishConnect()方法的解读如下:

@Override
        public final void finishConnect() {
            // Note this method is invoked by the event loop only if the connection attempt was
            // neither cancelled nor timed out.
            // 只有eventLoop线程才能调用finishConnect方法
            // 此方法在NioEventLoop的processSelectedKey()方法中被调用
            assert eventLoop().inEventLoop();

            try {
                boolean wasActive = isActive();
                /**
                 * 判断连接结果(由其子类完成)
                 * 通过SocketChannel的finishConnect()方法判断连接结果
                 * 连接成功返回true
                 * 连接失败抛异常
                 * 链路被关闭,链路中断等异常也属于连接失败
                 */
                doFinishConnect();
                // 负责将socketChannel修改为监听读操作位
                // 用来监听网络读事件
                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. AbstractNioByteChannel

AbstractNioByteChannel 类继承了AbstractNioChannel,因此也具备连接、注册等功能,但I/O读写依然交给子类。

public abstract class AbstractNioByteChannel extends AbstractNioChannel {
    private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
    private static final String EXPECTED_TYPES =
            " (expected: " + StringUtil.simpleClassName(ByteBuf.class) + ", " +
            StringUtil.simpleClassName(FileRegion.class) + ')';

    // 匿名内部类
    // 负责刷新发送缓存链表中的数据,由于write的数据没有直接写在Socket中,而是写在了ChannelOutboundBuffer缓存中,
    // 所以当调用flush() 方法时,会把数据写入Socket中并向网络中发送。
    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();
        }
    };
    private boolean inputClosedSeenErrorOnRead;

我们来看它的doWrite()方法,其主要作用是从ChannelOutboundBuffer缓存中获取发送的数据,进行循环发送:

  • 该写操作非常有意思,当多个线程同时进行写操作时,我们会给默认每个Channel每次循环16次去写数据,如果这16次内没有写完数据,则把选择Key的OP_WRITE事件从兴趣事件集中移除,并添加一个flushTask任务,先去执行其他任务,当检测到此任务时在发送
@Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        // 写请求自循环次数,默认为16次
        int writeSpinCount = config().getWriteSpinCount();
        do {
            // 获取当前Channel的缓存ChannelOutboundBuffer中的当前刷新消息
            Object msg = in.current();
            // 所有消息发送成功了
            if (msg == null) {
                // 清除Channel选择Key兴趣事件集中的OP_WRITE写操作事件
                // Wrote all messages.
                clearOpWrite();
                // 直接返回,没必要再添加写人物
                // Directly return here so incompleteWrite(...) is not called.
                return;
            }
            // 否则发送数据
            writeSpinCount -= doWriteInternal(in, msg);
        } while (writeSpinCount > 0);

        /**
         * 当因缓冲区满了而发送失败时
         * doWriteInternal返回Integer.MAX_VALUE
         * 此时writeSpinCount<0为true
         * 当发送16次还未全部发送完,但每次都写成功时
         * writeSpinCount 为0
         */
        incompleteWrite(writeSpinCount < 0);
    }
protected final void incompleteWrite(boolean setOpWrite) {
        // Did not write completely.
        // 将OP_WRITE写操作事件添加到Channel的选择Key兴趣事件集中
        if (setOpWrite) {
            setOpWrite();
        } 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.
            // 清除Channel选择Key兴趣事件集中的OP_WRITE写操作事件
            clearOpWrite();

            // 将操作任务添加到EventLoop线程上,以便后续继续发送
            // Schedule flush again later so other tasks can be picked up in the meantime
            eventLoop().execute(flushTask);
        }
    }
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (!buf.isReadable()) {
                // 若可读字节数为0,则从缓冲区中移除
                in.remove();
                return 0;
            }

            // 实际发送字节数据
            final int localFlushedAmount = doWriteBytes(buf);
            if (localFlushedAmount > 0) {
                // 更新字节数据的发送进度
                in.progress(localFlushedAmount);
                if (!buf.isReadable()) {
                    // 若可读字节数为0,则从缓存区中移除
                    in.remove();
                }
                return 1;
            }
        } else if (msg instanceof FileRegion) {
            FileRegion region = (FileRegion) msg;
            // 如果是文件FileRegion消息
            if (region.transferred() >= region.count()) {
                in.remove();
                return 0;
            }

            // 实际写操作
            long localFlushedAmount = doWriteFileRegion(region);
            if (localFlushedAmount > 0) {
                // 更新数据的发送进度
                in.progress(localFlushedAmount);
                if (region.transferred() >= region.count()) {
                    // 若region已全部发送成功
                    // 则从缓存中移除
                    in.remove();
                }
                return 1;
            }
        } else {
            // Should not reach here.
            // 不支持发送其他类型的数据
            throw new Error();
        }
        // 当实际发送字节数为0时,返回Integer.MAX_VALUE
        return WRITE_STATUS_SNDBUF_FULL;
    }

接下来我们来看一下read方法:

NioByteUnsafe的read()方法的实现思路大概分为以下3步。

(1)获取Channel的配置对象、内存分配器ByteBufAllocator,并计算内存分配器RecvByte BufAllocator.Handle。

(2)进入for循环。循环体的作用:使用内存分配器获取数据容器ByteBuf,调用doReadBytes()方法将数据读取到容器中,如果本次循环没有读到数据或链路已关闭,则跳出循环。另外,当循环次数达到属性METADATA的defaultMaxMessagesPerRead次数(默认为16)时,也会跳出循环。由于TCP传输会产生粘包问题,因此每次读取都会触发channelRead事件,进而调用业务逻辑处理Handler。

(3)跳出循环后,表示本次读取已完成。调用allocHandle的readComplete()方法,并记录读取记录,用于下次分配合理内存。

具体源码查看:

@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();
            // 清空上一次读取的字节数,每次读取时重新计算
            // 字节buf分配器,并计算字节buf分配器Handler
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    // 分配内存
                    byteBuf = allocHandle.allocate(allocator);
                    // doReadBytes()将数据读到容器中
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // 若本次没有读到数据或链路已关闭,则跳出循环
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // 当读到-1时,表示Channel通道已关闭
                            // 没必要再继续读
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }
                    // 更新读消息计数器
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    // 通知通道处理读取数据,触发Channel管道的fireChannelRead时间
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading());
                // 读取操作完毕
                allocHandle.readComplete();
                // 触发Channel管道的fireChannelReadComplete事件
                pipeline.fireChannelReadComplete();

                if (close) {
                    // 如果Socket通道关闭,则关闭读操作
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                // 处理读异常
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                // 若读操作完毕,且没有配置自动读
                // 则从选择Key兴趣中移除读操作时间
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

3.AbstractNioMessageChannel

AbstractNioMessageChannel 主要是操作对象类型,因此并不存在拆包粘包问题,下面看一下它的dowrite()和read()方法:

 @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        final SelectionKey key = selectionKey();
        // 获取Key兴趣集
        final int interestOps = key.interestOps();

        int maxMessagesPerWrite = maxMessagesPerWrite();
        while (maxMessagesPerWrite > 0) {
            Object msg = in.current();
            // 数据已全部发送完,从兴趣集中移除OP_WRITE事件
            if (msg == null) {
                break;
            }
            try {
                boolean done = false;
                // 获取配置中循环写的最大次数
                for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
                    // 调用子类方法,若msg写成功了,则返回true
                    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()) {
            // Wrote all messages.
            if ((interestOps & SelectionKey.OP_WRITE) != 0) {
                key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
            }
        } else {
            // Did not write all messages.
            if ((interestOps & SelectionKey.OP_WRITE) == 0) {
                key.interestOps(interestOps | SelectionKey.OP_WRITE);
            }
        }
    }

那么它的read()方法:

@Override
        public void read() {
            assert eventLoop().inEventLoop();
            // 获取Channel的配置对象
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            // 获取计算内存分配Handle
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            // 清空上次的记录
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        /**
                         * 调用子类的 doReadMessages()方法
                         * 读取数据包,并放入readBuf链表中
                         * 当成功读取时,返回1
                         */
                        int localRead = doReadMessages(readBuf);
                        // 已无数据,跳出循环
                        if (localRead == 0) {
                            break;
                        }
                        // 链路关闭,跳出循环
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        // 记录成功读取的次数
                        allocHandle.incMessagesRead(localRead);
                        // 默认不能超过16次
                    } while (continueReading(allocHandle));
                } 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();

                if (exception != null) {
                    // 处理channel 异常关闭
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                // Check if there is a readPending which was not processed yet.
                // This could be for two reasons:
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
                // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
                //
                // See https://github.com/netty/netty/issues/2254
                // 读操作完毕,且没有配置自动读
                if (!readPending && !config.isAutoRead()) {
                    // 移除读操作事件
                    removeReadOp();
                }
            }
        }

4.NioSocketChannel源码

NioSocketChannel是AbstractNioByteChannel的子类,也是SocketChannel接口实现类。

Netty中每个Socket连接都会生成一个NioSocketChannel对象。

关于NioSocketChannel的功能图:
在这里插入图片描述
NioSocketChannel的继承关系图:
在这里插入图片描述

解释:
• SocketChannel在NioSocketChannel构造方法中由SelectorProvider.provider().openSocket Channel()创建,提供javaChannel()方法以获取SocketChannel。

• 实现doReadBytes()方法,从SocketChannel中读取数据。

• 重写doWrite()方法、实现doWriteBytes()方法,将数据写入Socket中。

• 实现doConnect()方法和客户端连接。

来看几个核心的方法:

@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());
    }
  @Override
    protected int doWriteBytes(ByteBuf buf) throws Exception {
        // 获取buf的可读字节数
        final int expectedWrittenBytes = buf.readableBytes();
        // 把buf写入Socket缓存中,返回写入字节数
        return buf.readBytes(javaChannel(), expectedWrittenBytes);
    }
@Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        // 获取SocketChannel
        SocketChannel ch = javaChannel();
        // 获取循环写的最大次数
        int writeSpinCount = config().getWriteSpinCount();
        do {
            // 缓存数据为空,无数据可写
            if (in.isEmpty()) {
                // 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();
            /**
             * 缓存由多个Entry组成,每次写时都可能写多个Entry
             * 具体一次性发送多少数据
             * 由byteBuffer数组的最大长度和一次最大可写字节数决定
             */
            ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
            int nioBufferCnt = in.nioBufferCount();

            // Always use nioBuffers() to workaround data-corruption.
            // See https://github.com/netty/netty/issues/2761
            // 缓存中有多少个nioBuffer
            switch (nioBufferCnt) {
                case 0:
                    // We have something else beside ByteBuffers to write so fallback to normal writes.
                    // 非ByteBuffer数据,交给父类实现
                    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];
                    // buf可读字节数
                    int attemptedBytes = buffer.remaining();
                    // 把buf发送到socket缓存中
                    final int localWrittenBytes = ch.write(buffer);
                    // 发送失败
                    if (localWrittenBytes <= 0) {
                        // 将写事件添加到事件兴趣集中
                        incompleteWrite(true);
                        return;
                    }
                    /**
                     * 根据成功写入字节数和尝试写入字节数调整下次最大可写字节数
                     * 当两者相等时,若尝试写入字节数*2 大于当前最大写入字节数
                     * 则下次最大可写字节数等于尝试写入字节数*2
                     * 当两者不相等时,成功写入字节数小于尝试写入字节数/2
                     * 且尝试接入字节数大于4096时
                     * 下次最大可写字节数等于尝试写入字节数/2
                     */
                    adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
                    // 从缓存中移除写入字节数
                    in.removeBytes(localWrittenBytes);
                    // 循环写次数减1
                    --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();
                    // 真正发送到Socket缓存中的字节数
                    final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
                    // 如果发送失败
                    if (localWrittenBytes <= 0) {
                        // 将写事件添加到事件兴趣集中
                        // 以便下次NioEventLoop继续触发写操作
                        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);
                    // 循环写次数减1
                    --writeSpinCount;
                    break;
                }
            }
        } while (writeSpinCount > 0);
        /**
         * 未全部发送完:
         * 若writeSpinCount 《 0
         * 则说明Socket缓冲区已满,未发送成功
         * 若writeSpinCount = 0
         * 则说明Netty缓存数据太大,写了16次还未写完
         */
        incompleteWrite(writeSpinCount < 0);
    }

参考书籍:《Netty源码剖析与应用》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值