netty5源码探索(二)----AbstractByteBuf

来源:互联网 发布:linux学习文档 编辑:程序博客网 时间:2024/06/06 08:29

还是放上ByteBuf的类图,没图没有安全感。默默的感觉到这图会陪伴长久有木有得意

这里还是推荐下一个自己做得netty+spring的集成方案,优化netty配置启动,并提供基础服务器搭建的配置+极少代码的实现方案。

http://download.csdn.net/detail/jackieliyido/9497093




昨天主要对ByteBuf的注释做了一个翻译,在大体上对ByteBuf有了初步了解,关键点还是在readerIndex以及writerIndex在各个方法下的状态。这两个关键索引也将会贯穿接下来几个类的始末。

今天咱们再来看下AbstractByteBuf. 在这里有了ByteBuf最初的实现(不知道为啥,脑子里出现了EVA场景,人类补完得意)。本人的思路是一步一步来,现在的书都太快进了,一步直接跨越到heapByteBuf啥啥,从过去一年多来,至少自己是深深体会到了自学时的困难。


正题开始:先看AbstractByteBuf的第一段代码

/** * A skeletal implementation of a buffer. */public abstract class AbstractByteBuf extends ByteBuf {    static final ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(ByteBuf.class);    int readerIndex;    int writerIndex;    private int markedReaderIndex;    private int markedWriterIndex;    private int maxCapacity;    private SwappedByteBuf swappedBuf;    protected AbstractByteBuf(int maxCapacity) {        if (maxCapacity < 0) {            throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)");        }        this.maxCapacity = maxCapacity;    }
第一句话就很重要:static final leakDetector,内存泄漏管理器,还是static final的,这意味着所有继承自AbstractByteBuf的类都将共享一个内存泄漏管理

ResourceLeakDetector 是netty自带的内存泄漏管理器。笔者粗粗看了下里面的逻辑,嗯,比较麻烦,决定在看完所有ByteBuf后回头再来看这个管理器。害羞

看成员变量,两个熟悉的读写索引,一个上一篇提到的mark索引,最大容量(注意不是当前容量),swappedByteBuf. 嗯,最后一个是什么鬼?查下源码:

/** * Wrapper which swap the {@link ByteOrder} of a {@link ByteBuf}. */public class SwappedByteBuf extends ByteBuf {    private final ByteBuf buf;    private final ByteOrder order;    public SwappedByteBuf(ByteBuf buf) {        if (buf == null) {            throw new NullPointerException("buf");        }        this.buf = buf;        if (buf.order() == ByteOrder.BIG_ENDIAN) {            order = ByteOrder.LITTLE_ENDIAN;        } else {            order = ByteOrder.BIG_ENDIAN;        }    }
原来是调整大小端的。这里有个大小端概念,C,C++蛮多使用小端的,而我们JAVA默认使用大端。什么意思?比如我要发一个18,两个字节就是0x0012,对于小端模式,先发0x12后发0x00,也就是我们先收到12后收到00,对于java,TCP默认的是大端,即先发高位0x00,后发0x12,netty默认大端,即如果按照大端发送过来的数据,可直接转换成对应数值。这一块比较搞,笔者也是最近和C端工程师调试硬件设备的时候关注这块0.0,有兴趣的自己百度下。


嗯,接下来比较纠结,1000多行代码,全部说篇幅太长了,只能像书里一样略过很多了。不过这里追加一些就是。

代码中有很多方法实现内容仅仅是对我们的索引做修改并返回this或索引值,此类只需要明白ByteBuf的读写操作范围由索引决定即可。举个例子:

    @Override    public ByteBuf setIndex(int readerIndex, int writerIndex) {        if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity()) {            throw new IndexOutOfBoundsException(String.format(                    "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",                    readerIndex, writerIndex, capacity()));        }        this.readerIndex = readerIndex;        this.writerIndex = writerIndex;        return this;    }    @Override    public ByteBuf clear() {        readerIndex = writerIndex = 0;        return this;    }    @Override    public boolean isReadable() {        return writerIndex > readerIndex;    }    @Override    public boolean isReadable(int numBytes) {        return writerIndex - readerIndex >= numBytes;    }
摘了4个方法,可以看到,ByteBuf控制读写区域都是通过控制索引来的,clear即将readerIndex,writerIndex置0. 注意,得提醒下:我们的内部成员还包括markedReaderIndex和markedWriterIndex,clear并没有将marked索引清零

关于marked,也需要提一下,看相关代码:

 @Override    public ByteBuf markReaderIndex() {        markedReaderIndex = readerIndex;        return this;    }    @Override    public ByteBuf resetReaderIndex() {        readerIndex(markedReaderIndex);        return this;    }
markReaderIndex,标记读索引,写也一样,就不贴了。涉及到的方法readerIndex(int readerIndex)就是按照指定值设置readerIndex。这里在reset时就是将readerIndex set为markedReaderIndex.


==========比较重要的分割线==========

复杂的上来了,还记得在上一篇中有说discardReaderIndex,么?就是这个图:

 *  BEFORE discardReadBytes() * *      +-------------------+------------------+------------------+ *      | discardable bytes |  readable bytes  |  writable bytes  | *      +-------------------+------------------+------------------+ *      |                   |                  |                  | *      0      <=      readerIndex   <=   writerIndex    <=    capacity * * *  AFTER discardReadBytes() * *      +------------------+--------------------------------------+ *      |  readable bytes  |    writable bytes (got more space)   | *      +------------------+--------------------------------------+ *      |                  |                                      | * readerIndex (0) <= writerIndex (decreased)        <=        capacity
怎么做到的?其实也算是比较绕的,看着图简单啊,把左边的区域挪到右边,实际上做了什么动作?在discard以后marked索引会如何变化?来看源码实现:
  @Override    public ByteBuf discardReadBytes() {        ensureAccessible();        if (readerIndex == 0) {            return this;        }        if (readerIndex != writerIndex) {            setBytes(0, this, readerIndex, writerIndex - readerIndex);            writerIndex -= readerIndex;            adjustMarkers(readerIndex);            readerIndex = 0;        } else {            adjustMarkers(readerIndex);            writerIndex = readerIndex = 0;        }        return this;    }
一行一行来,不要漏过,第一句话ensureAccessible();保证可以进入,做了什么事
/**     * Should be called by every method that tries to access the buffers content to check     * if the buffer was released before.     */    protected final void ensureAccessible() {        if (refCnt() == 0) {            throw new IllegalReferenceCountException(0);        }    }
提示说,需要确保在每一个需要进入buffers操作的方法里都需要去检查这块buffer是否已经被释放了。ensureAccessible方法中涉及到另一个方法refCnt(),还记得ByteBuf实现了什么接口么?有一个ReferenceCounted接口,关于引用计数的,这里就是判断引用计数是否为0。

继续往下看,readerIndex==0,return this; 如果readerIndex已经在0了,就不再处理直接返回。

readerIndex!=writerIndex时,setBytes(0, this, readerIndex, writerIndex - readerIndex);

看下ByteBuf中对setBytes方法的定义。

/**     * Transfers the specified source buffer's data to this buffer starting at     * the specified absolute {@code index}.     * This method does not modify {@code readerIndex} or {@code writerIndex}     * of both the source (i.e. {@code this}) and the destination.     *     * @param srcIndex the first index of the source     * @param length   the number of bytes to transfer     *     * @throws IndexOutOfBoundsException     *         if the specified {@code index} is less than {@code 0},     *         if the specified {@code srcIndex} is less than {@code 0},     *         if {@code index + length} is greater than     *            {@code this.capacity}, or     *         if {@code srcIndex + length} is greater than     *            {@code src.capacity}     */    public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length);
将ByteBuf src 中从srcIndex开始向后length长度段的数据复制到原来的ByteBuf中,以index为新的readerIndex起始点。重点:这还是原先的BUFFER

在AbstractByteBuf中没有发现此方法的实现,因此我们等会儿向下索求。

接下来将写索引减去读索引,同时调整mark并将readerIndex置为0。

protected final void adjustMarkers(int decrement) {        int markedReaderIndex = this.markedReaderIndex;        if (markedReaderIndex <= decrement) {            this.markedReaderIndex = 0;            int markedWriterIndex = this.markedWriterIndex;            if (markedWriterIndex <= decrement) {                this.markedWriterIndex = 0;            } else {                this.markedWriterIndex = markedWriterIndex - decrement;            }        } else {            this.markedReaderIndex = markedReaderIndex - decrement;            markedWriterIndex -= decrement;        }    }
到此discardReadBytes结束。


我们细致研究下setBytes怎么实现的。向下检索ByteBuf子类。

一直到UnpooledDirectByteBuf这一级,我们才发现有具体的实现,此时也突然清醒,ByteBuf在实现上大致分为HeapByteBuf以及DirectByteBuf,也就是堆内存和直接内存,不同的实现方式导致ByteBuf操作的代码各有不同。这里我们以UnpooledDirectByteBuf为例分析下setBytes到底做了什么事情。

 @Override    public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {        checkSrcIndex(index, length, srcIndex, src.capacity());        if (src.nioBufferCount() > 0) {            for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) {                int bbLen = bb.remaining();                setBytes(index, bb);                index += bbLen;            }        } else {            src.getBytes(srcIndex, this, index, length);        }        return this;    }
第一句, checkSrcIndex(index, length, srcIndex, src.capacity());检查了当前ByteBuf的应用计数是否为0,同时检查了输入参数的合法性,相互间大小关系

src.nioBufferCount()>0,也就是包含至少一个java.nio.ByteBuffer, 此时,遍历nio buffer,并通过setBytes(int index, ByteBuffer src)创建新的ByteBuffer,然后循环过程中通过index +=bbLen控制在新的ByteBuffer后面追加数据。关键就在这,setBytes(int index, ByteBuffer src)做了什么?是copy么?看源码:

@Override    public ByteBuf setBytes(int index, ByteBuffer src) {        ensureAccessible();        ByteBuffer tmpBuf = internalNioBuffer();        if (src == tmpBuf) {            src = src.duplicate();        }        tmpBuf.clear().position(index).limit(index + src.remaining());        tmpBuf.put(src);        return this;    }    private ByteBuffer internalNioBuffer() {        ByteBuffer tmpNioBuf = this.tmpNioBuf;        if (tmpNioBuf == null) {            this.tmpNioBuf = tmpNioBuf = buffer.duplicate();        }        return tmpNioBuf;    }
是中间变量ByteBuffer tmpNioBuf这段代码看了很久,想了很久才明白tmpBuf是buffer的duplicate,所以对于temBuf的操作就会直接影响到buffer.
继续往下看,如果不含java.nio.Buffer 则调用getBytes(int index, ByteBuf dst, int dstIndex, int length);
@Override    public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {        checkDstIndex(index, length, dstIndex, dst.capacity());        if (dst.hasArray()) {            getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);        } else if (dst.nioBufferCount() > 0) {            for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {                int bbLen = bb.remaining();                getBytes(index, bb);                index += bbLen;            }        } else {            dst.setBytes(dstIndex, this, index, length);        }        return this;    }
在getBytes()中,因为是不含nio Buffer,因此调用 getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);也就是将原先的指定内容duplicate到dst.array中。

好吧,到此为止我们明白了 setBytes(0, this, readerIndex, writerIndex - readerIndex);具体怎么操作的,大致上可以认为:使用中间变量对原来的buffer做一个duplicate,然后读取原buffer的指定内容至的tempBuf中


======WriteBytes=====

 @Override    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {        ensureAccessible();        ensureWritable(length);        setBytes(writerIndex, src, srcIndex, length);        writerIndex += length;        return this;    }
这一块有不一样的地方了,在《netty权威指南》中很尴尬地发现书中并没有ensureAccessible()...鄙视

ensureWritable(length),确认是否可写,如果写入长度小于当前Byte可写的字节数,则说明可以成功写入,不需要扩展,如果大于最大容量的剩余可写字节数,则抛出IndexOutOfBoundException。如果大于可写字节数,又小于最大容量下的可写字节数,就进行扩容。

 @Override    public ByteBuf ensureWritable(int minWritableBytes) {        if (minWritableBytes < 0) {            throw new IllegalArgumentException(String.format(                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));        }        if (minWritableBytes <= writableBytes()) {            return this;        }        if (minWritableBytes > maxCapacity - writerIndex) {            throw new IndexOutOfBoundsException(String.format(                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",                    writerIndex, minWritableBytes, maxCapacity, this));        }        // Normalize the current capacity to the power of 2.        int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);        // Adjust to the new capacity.        capacity(newCapacity);        return this;    }
最后 setBytes(writerIndex, src, srcIndex, length);写入新的数据,并调整writerIndex.

在这里,还有一个新的方法:ByteBufAllocator.calculateNewCapacity()

/**     * Calculate the new capacity of a {@link ByteBuf} that is used when a {@link ByteBuf} needs to expand by the     * {@code minNewCapacity} with {@code maxCapacity} as upper-bound.     */    int calculateNewCapacity(int minNewCapacity, int maxCapacity);
buffer分配器在子类中根据不同类型各自实现,扩展上限为maxCapacity.


=====skipBytes=====

skip较为简单了,校验是否具有length数量的可读字节,如果有则将readerIndex +=length.

这一块与《netty权威指南》又有出入,也不知道是不是netty版本问题疑问

@Override    public ByteBuf skipBytes(int length) {        checkReadableBytes(length);        readerIndex += length;        return this;    }


到此为止,AbstractByteBuf几个重要方法看完了。基础是对于索引的操作,以及discard和write上。

下一篇我们继续看他的子类,AbstractReferenceCountedByteBuf.
0 0
原创粉丝点击