flush也是从headContext的unsafe的flush开始的:
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
unsafe.flush();
}
然后看unsafe的flush:
@Override
public final void flush() {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
return;
}
outboundBuffer.addFlush();
flush0();
}
然后这里就开始了,我们把flush分为三部分:
1、添加刷新标志并设置写状态
2、遍历buffer队列,过滤ByteBuf
3、调用jdk底层的api进行自旋写
一、添加刷新标志并设置写状态
在上一篇文章我们添加了很多unflushedEntry的entry节点,这里我们要把这些flushedEntry的entry节点设置为flushed。
先看outboundBuffer.addFlush():
/**
* Add a flush to this {@link ChannelOutboundBuffer}. This means all previous added messages are marked as flushed
* and so you will be able to handle them.
*/
public void addFlush() {
// There is no need to process all entries if there was already a flush before and no new messages
// where added in the meantime.
//
// See https://github.com/netty/netty/issues/2577
Entry entry = unflushedEntry;
if (entry != null) {
if (flushedEntry == null) {
// there is no flushedEntry yet, so start with the entry
flushedEntry = entry;
}
do {
flushed ++;
if (!entry.promise.setUncancellable()) {
// Was cancelled so make sure we free up memory and notify about the freed bytes
int pending = entry.cancel();
decrementPendingOutboundBytes(pending, false, true);
}
entry = entry.next;
} while (entry != null);
// All flushed so reset unflushedEntry
unflushedEntry = null;
}
}
如果还有unflushedEntry那就进入循环:
如果是第一次flushed,也就是flushedEntry为空,那就设置entry为flushed。否则先把flushed的数量++,然后调用entry的下一个。
最后设置所有的entry为flushed,unflushedEntry就为空了。
中间还有一个decrementPendingOutboundBytes,如果取消了,就要把我们的可写的标志重新设置,和上一篇文章的第三部分对应,只是一个相反的过程而已:
/**
* Decrement the pending bytes which will be written at some point.
* This method is thread-safe!
*/
void decrementPendingOutboundBytes(long size) {
decrementPendingOutboundBytes(size, true, true);
}
private void decrementPendingOutboundBytes(long size, boolean invokeLater, boolean notifyWritability) {
if (size == 0) {
return;
}
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
if (notifyWritability && newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) {
setWritable(invokeLater);
}
}
经过这个操作之后,unflushedEntry为null,flushedEntry到tailEntry之间都是flush过的。
二、遍历buffer队列,过滤ByteBuf
然后进入flush0方法:
@SuppressWarnings("deprecation")
protected void flush0() {
if (inFlush0) {
// Avoid re-entrance
return;
}
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null || outboundBuffer.isEmpty()) {
return;
}
inFlush0 = true;
// Mark all pending write requests as failure if the channel is inactive.
if (!isActive()) {
try {
if (isOpen()) {
outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true);
} else {
// Do not trigger channelWritabilityChanged because the channel is closed already.
outboundBuffer.failFlushed(FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
}
} finally {
inFlush0 = false;
}
return;
}
try {
doWrite(outboundBuffer);
} catch (Throwable t) {
if (t instanceof IOException && config().isAutoClose()) {
/**
* Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of
* failing all flushed messages and also ensure the actual close of the underlying transport
* will happen before the promises are notified.
*
* This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()}
* may still return {@code true} even if the channel should be closed as result of the exception.
*/
close(voidPromise(), t, FLUSH0_CLOSED_CHANNEL_EXCEPTION, false);
} else {
outboundBuffer.failFlushed(t, true);
}
} finally {
inFlush0 = false;
}
}
如果是正在flush了,那就返回,这一段代码我们只看doWrite方法,AbstractNioByteChannel的实现:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = -1;
boolean setOpWrite = false;
for (;;) {
Object msg = in.current();
if (msg == null) {
// Wrote all messages.
clearOpWrite();
// Directly return here so incompleteWrite(...) is not called.
return;
}
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
in.remove();
continue;
}
boolean done = false;
long flushedAmount = 0;
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i --) {
int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
flushedAmount += localFlushedAmount;
if (!buf.isReadable()) {
done = true;
break;
}
}
in.progress(flushedAmount);
if (done) {
in.remove();
} else {
// Break the loop and so incompleteWrite(...) is called.
break;
}
} else if (msg instanceof FileRegion) {
...
} else {
// Should not reach here.
throw new Error();
}
}
incompleteWrite(setOpWrite);
}
不停地循环处理当前的数据。
我们不关心FileRegion,FileRegion是从文件里面写的,先不管,我们看msg instanceof ByteBuf这一个部分。看看in.current是什么东西:
/**
* Return the current message to write or {@code null} if nothing was flushed before and so is ready to be written.
*/
public Object current() {
Entry entry = flushedEntry;
if (entry == null) {
return null;
}
return entry.msg;
}
哈哈,是我们之前设置flushed的entry。
三、调用jdk底层的api进行自旋写
接着上面,我们开始调用自旋写了,自旋的次数是多少呢?从writeSpinCount = config().getWriteSpinCount();里面可以看出来。
找一找就是16。
最重要的是doWriteBytes方法,看NioSocketChannel方法的实现:
@Override
protected int doWriteBytes(ByteBuf buf) throws Exception {
final int expectedWrittenBytes = buf.readableBytes();
return buf.readBytes(javaChannel(), expectedWrittenBytes);
}
这里的javaChannel就是原生的java的channel了。
看PooledDirectByteBuf的实现:
@Override
public int readBytes(GatheringByteChannel out, int length)
throws IOException {
checkReadableBytes(length);
int readBytes = getBytes(readerIndex, out, length);
readerIndex += readBytes;
return readBytes;
}
getBytes做什么?看PooledDirectByteBuf的实现:
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
checkIndex(index, length);
if (length == 0) {
return 0;
}
ByteBuffer tmpBuf;
if (internal) {
tmpBuf = internalNioBuffer();
} else {
tmpBuf = memory.duplicate();
}
index = idx(index);
tmpBuf.clear().position(index).limit(index + length);
return out.write(tmpBuf);
}
创建一个jdk地城的buffer就是ByteBuffer,然后把我们的byteBuf给它,就是这么简单。out.write方法已经是底层的了。
回来吧:
for (int i = writeSpinCount - 1; i >= 0; i --) {
int localFlushedAmount = doWriteBytes(buf);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
flushedAmount += localFlushedAmount;
if (!buf.isReadable()) {
done = true;
break;
}
}
调用doWriteBytes之后,localFlushAmount是返回值,如果这个值是0,表明jdk底层暂时也不可写,跳出来。
如果不为0,那就添加flushedAmount的数量。
如果buf都不可读了,那就是完成了,done为true。
继续看下面的代码:
if (done) {
in.remove();
} else {
// Break the loop and so incompleteWrite(...) is called.
break;
}
刚刚说到done为true之后,需要这个in移除:
/**
* Will remove the current message, mark its {@link ChannelPromise} as success and return {@code true}. If no
* flushed message exists at the time this method is called it will return {@code false} to signal that no more
* messages are ready to be handled.
*/
public boolean remove() {
Entry e = flushedEntry;
if (e == null) {
clearNioBuffers();
return false;
}
Object msg = e.msg;
ChannelPromise promise = e.promise;
int size = e.pendingSize;
removeEntry(e);
if (!e.cancelled) {
// only release message, notify and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);
safeSuccess(promise);
decrementPendingOutboundBytes(size, false, true);
}
// recycle the entry
e.recycle();
return true;
}
看removeEntry(e);
private void removeEntry(Entry e) {
if (-- flushed == 0) {
// processed everything
flushedEntry = null;
if (e == tailEntry) {
tailEntry = null;
unflushedEntry = null;
}
} else {
flushedEntry = e.next;
}
}
flushedEntry一直走到丢列的最后,如果需要flush的值为0了,flushedEntry就置空,如果刚好是空,那就tailEntry和unflushedEntry都置为空了。