Netty的writeAndFlush流程分析
1、Write操作
writeAndFlush是一个典型的出站操作,如果调用者是channel则从tail节点向前传播。
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
看TailContext的writeAndFlush方法。
会调用write方法,其中flush参数的值为true。
先调用write方法,传播一个write事件,然后再调用flush方法。
调用HeadContext中的write方法。
主要进行两步操作,消息过滤,如果使用的不是直接内存需要申请一块直接内存将msg包装一下,然后,添加到channelOutBounfBuffer中,这是一个链表结构。
因此在传输数据的时候,最好使用DirectByteBuf可以减少一次拷贝。为什么需要这次拷贝呢?因为堆内存是会随着垃圾回收动态变化的,数据拷贝的时候需要保证内存地址不变。
ChannelOutboundBuffer 缓存是一个链表结构,每次传入的数据都会被封装成一个 Entry 对象添加到链表中。ChannelOutboundBuffer 包含三个非常重要的指针:第一个被写到缓冲区的节点 flushedEntry、第一个未被写到缓冲区的节点 unflushedEntry和最后一个节点 tailEntry。
在初始状态下这三个指针都指向 NULL,当我们每次调用 write 方法是,都会调用 addMessage 方法改变这三个指针的指向,可以参考下图理解指针的移动过程会更加形象。
ChannelOutboundBuffer包装了bytebuf,如果一直往里面写数据,可能会造成OOM,因此Netty里面引入了高低水位,也就是每次写一个entry后,会计算当前的字节数如果超过了高水位,就会设置不可写。
但是这个不可写的话,需要用户去手动判断 channel.isWriteAble,否则的话,还是可以继续写的。
2、Flush操作
flush操作就是将缓冲区的数据写到socket缓冲区的过程。将用户缓冲区的数据写到内核缓冲区。
HeadContext的flush:
主要包含两个重要方法,addflush和flush0。
addflush用于移动指针,并且减少channelOutBoundBuffer中的字节数。
当减少到小于低水位的时候,就标记为可写。
flush0 主要完成刷新的操作,因为数据量可能很大,如果一直写的话,会阻塞netty的work线程,这里引入了一个自选锁:
如果写完了,需要清除write事件,否则的话,下一轮继续写。