Netty源码分析——写流程

783 阅读2分钟

Netty源码分析——写流程

前言

我们之前的分析中,其实已经讲过了,调用channel或者pipelinewrite或者writeAndFlush会最终从tailContext流经管道中的所有OutBoundHandler,最终走到HeadContext里。

我们首先要明确一个概念,就是write操作并不会直接把数据写进socket,Netty中有一个缓冲区的概念,数据是先写入缓冲区的,并且缓冲区的大小是可以设置的,我们看一下这个流程。

write

HeadContextwrite操作是委托给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又在搞链表了!

先看看几种节点:

entry | center

说下这三种节点,直接看注释也可以:

  1. flushedEntry指针表示第一个被写到操作系统Socket缓冲区中的节点
  2. unFlushedEntry指针表示第一个未被写入到操作系统Socket缓冲区中的节点
  3. tailEntry指针表示ChannelOutboundBuffer缓冲区的最后一个节点

调用addMessage之前,三个节点都为null,调用addMessage后:

就从这里我们其实大概也能猜到,只要不进行flush操作,flushedEntry节点就会为Null,我们再进行一次write操作,这时候的模样:

这里我强烈建议一下看文章的同学这里debug一下,比较容易理解。

至此write操作就结束了。会看一下,其实就是把要写的数据(ByteBuf或者FileRegion)封装成一个Entry,添加到ChannelOutboundBuffer的一个链表里。