不管我们之前处理没处理我们的对象,也就是说不管我们有没有使用编码器,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操作,不停地把这个不可写的状态传递下去。