还是放上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.