Netty源码分析——写流程
前言
我们之前的分析中,其实已经讲过了,调用channel或者pipeline的write或者writeAndFlush会最终从tailContext流经管道中的所有OutBoundHandler,最终走到HeadContext里。
我们首先要明确一个概念,就是write操作并不会直接把数据写进socket,Netty中有一个缓冲区的概念,数据是先写入缓冲区的,并且缓冲区的大小是可以设置的,我们看一下这个流程。
write
HeadContext的write操作是委托给unsafe的,在AbstractChannel里:
@Override
public final void write(Object msg, ChannelPromise promise) {
//保证是在Reactor线程中调用到这里的
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
// 如果没有buffer,则写入失败,并且释放消息
safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
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);
}
之前几步毕竟简单,可以看到,如果没有outboundBuffer,写操作是直接失败的。
下面先过滤消息,看下AbstractNioByteChannel#filterOutboundMessage:
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或者FileRegion。这里会把所有的非直接内存转换成直接内存(DirectBuffer)。
继续看,后面是向buffer中写入消息:
public void addMessage(Object msg, int size, ChannelPromise promise) {
Entry entry = Entry.newInstance(msg, size, total(msg), promise);
if (tailEntry == null) {
flushedEntry = null;
} else {
Entry tail = tailEntry;
tail.next = entry;
}
tailEntry = entry;
if (unflushedEntry == null) {
unflushedEntry = entry;
}
incrementPendingOutboundBytes(entry.pendingSize, false);
}
这里看着这个又是Entry又是什么tail的字眼,大概就能猜出来——Netty又在搞链表了!
先看看几种节点:
说下这三种节点,直接看注释也可以:
flushedEntry指针表示第一个被写到操作系统Socket缓冲区中的节点unFlushedEntry指针表示第一个未被写入到操作系统Socket缓冲区中的节点tailEntry指针表示ChannelOutboundBuffer缓冲区的最后一个节点
调用addMessage之前,三个节点都为null,调用addMessage后:
就从这里我们其实大概也能猜到,只要不进行flush操作,flushedEntry节点就会为Null,我们再进行一次write操作,这时候的模样:
这里我强烈建议一下看文章的同学这里debug一下,比较容易理解。
至此write操作就结束了。会看一下,其实就是把要写的数据(ByteBuf或者FileRegion)封装成一个Entry,添加到ChannelOutboundBuffer的一个链表里。


