netty源码阅读之编码之write写buffer队列

不管我们之前处理没处理我们的对象,也就是说不管我们有没有使用编码器,wriiteAndFlush之后,最终都会调用到headcontext的write和flush方法。

我们把这个headcontext的方法分为以下几个步骤:

1、direct化ByteBuf

2、插入写队列

3、设置写状态

这是我们headContext的write方法:

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

调用了unsafe的write方法,看实现:

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

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                // If the outboundBuffer is null we know the channel was closed and so
                // need to fail the future right away. If it is not null the handling of the rest
                // will be done in flush0()
                // See https://github.com/netty/netty/issues/2362
                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);
        }

这个就是我们要分析的源码。

一、direct化ByteBuf

我们看AbstractNioByteChannel的filterOutboundMessage方法的实现:

    @Override
    protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.isDirect()) {
                return msg;
            }

            return newDirectBuffer(buf);
        }

        if (msg instanceof FileRegion) {
            return msg;
        }

        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }

首先判断是否ByteBuf,如果是,那就强转为ByteBuf,然后如果是Direct就不处理,否则调用newDirectBuffer将byteBuf direct化。

    /**
     * Returns an off-heap copy of the specified {@link ByteBuf}, and releases the original one.
     * Note that this method does not create an off-heap copy if the allocation / deallocation cost is too high,
     * but just returns the original {@link ByteBuf}..
     */
    protected final ByteBuf newDirectBuffer(ByteBuf buf) {
        final int readableBytes = buf.readableBytes();
        if (readableBytes == 0) {
            ReferenceCountUtil.safeRelease(buf);
            return Unpooled.EMPTY_BUFFER;
        }

        final ByteBufAllocator alloc = alloc();
        if (alloc.isDirectBufferPooled()) {
            ByteBuf directBuf = alloc.directBuffer(readableBytes);
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(buf);
            return directBuf;
        }

        final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
        if (directBuf != null) {
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(buf);
            return directBuf;
        }

        // Allocating and deallocating an unpooled direct buffer is very expensive; give up.
        return buf;
    }

这个过程就是:如果readableBytes为0,就释放之前的对象并新建空Buffer对象返回。否则就新建direct对象返回,并且释放之前的。看到最后的注释,由于unpooled的分配比较耗时,所以不考虑这种情况,直接返回原来的对象。

二、插入写队列

会到代码的outboundBuffer.addMessage方法:

    /**
     * Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once
     * the message was written.
     */
    public void addMessage(Object msg, int size, ChannelPromise promise) {
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
        if (tailEntry == null) {
            flushedEntry = null;
            tailEntry = entry;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
            tailEntry = entry;
        }
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }

        // increment pending bytes after adding message to the unflushed arrays.
        // See https://github.com/netty/netty/issues/1619
        incrementPendingOutboundBytes(size, false);
    }

这一段看起来有很多指针,我们理清楚这些指针几乎就清晰代码的意思了。看tailEntry的解释就能一起看到其他的:

    // Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
    //
    // The Entry that is the first in the linked-list structure that was flushed
    private Entry flushedEntry;
    // The Entry which is the first unflushed in the linked-list structure
    private Entry unflushedEntry;
    // The Entry which represents the tail of the buffer
    private Entry tailEntry;

其中这里有三个变量在一个队列里面。

flushedEntry代表第一个被flush的Entry,unflushedEntry代表第一个没有被flush的Entry,tailEntry代表尾节点。

从flushedEntry到unflushedEntry之间(不包括unflushedEntry)都是被flush的Entry,从unflushedEntry到tailEntry都是未被flush的Entry。

上面那些指针指来指去,无非做两件事:

1、第一次调用write:

flushedEntry=null

unflushedEntry=entry

tailEntry=entry

2、后面调用write:

把新进来的entry放到tailEntry后面,然后把tailEntry指导新进来的entry里面。

三、设置写状态

在第二部分的最后代码这里还有一个:

        // increment pending bytes after adding message to the unflushed arrays.
        // See https://github.com/netty/netty/issues/1619
        incrementPendingOutboundBytes(size, false);

我们这个flush的缓冲队列总是有一定的容量的,不可能无限写,所以容量到达一定的程度,就要设置不可写。这个方法就做这个事情:

    /**
     * Increment the pending bytes which will be written at some point.
     * This method is thread-safe!
     */
    void incrementPendingOutboundBytes(long size) {
        incrementPendingOutboundBytes(size, true);
    }

    private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
        if (size == 0) {
            return;
        }

        long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
        if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
            setUnwritable(invokeLater);
        }
    }

我们通过TOTAL_PENDING_SIZE_UPDATER这个updater能知道这个数据的大小,如果超过了配置的大小也就是这个值: channel.config().getWriteBufferHighWaterMark()我们就要通过setUnwritable(invokeLater);设置不可写。我们现在看看这个配置是多少,一路跟踪getWriteBufferHighWaterMark()我们得到:


    private static final int DEFAULT_LOW_WATER_MARK = 32 * 1024;
    private static final int DEFAULT_HIGH_WATER_MARK = 64 * 1024;

    public static final WriteBufferWaterMark DEFAULT =
            new WriteBufferWaterMark(DEFAULT_LOW_WATER_MARK, DEFAULT_HIGH_WATER_MARK, false);

最高了64*1024也就是64k就设置为不可写,后面会讲到,到小于32k可以重新设置为可写状态。

现在我们看setUnwriteable(invokeLate)这个方法吧:

    private void setUnwritable(boolean invokeLater) {
        for (;;) {
            final int oldValue = unwritable;
            final int newValue = oldValue | 1;
            if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
                if (oldValue == 0 && newValue != 0) {
                    fireChannelWritabilityChanged(invokeLater);
                }
                break;
            }
        }
    }

就是通过cas操作,不停地把这个不可写的状态传递下去。

猜你喜欢

转载自blog.csdn.net/fst438060684/article/details/82953352