
一文搞懂Netty发送数据全流程 | 你想知道的细节全在这里(七)
4.2 flush事件的处理
客户端channel pipeline结构.png
最终flush事件会在pipeline中一直向前传播至HeadContext中,并在 HeadContext 里调用 channel 的 unsafe 类完成 flush 事件的最终处理逻辑。
下面就真正到了 Netty 处理 flush 事件的地方。
4.2.1 ChannelOutboundBuffer#addFlush
ChannelOutboundBuffer结构.png
这里就到了真正要发送数据的时候了,在 addFlush 方法中会将 flushedEntry 指针指向 unflushedEntry 指针表示的第一个未被 flush 的 Entry 节点。并将 unflushedEntry 指针置为空,准备开始 flush 发送数据流程。
此时 ChannelOutboundBuffer 由待发送数据的缓冲队列变为了即将要 flush 进 Socket 的数据队列
这样在 flushedEntry 与 tailEntry 之间的 Entry 节点即为本次 flush 操作需要发送的数据范围。
在 flush 发送数据流程开始时,数据的发送流程就不能被取消了,在这之前我们都是可以通过 ChannelPromise 取消数据发送流程的。
所以这里需要对 ChannelOutboundBuffer 中所有 Entry 节点包裹的 ChannelPromise 设置为不可取消状态。
如果这里的 setUncancellable() 方法返回 false 则说明在这之前用户已经将 ChannelPromise 取消掉了,接下来就需要调用 entry.cancel() 方法来释放为待发送数据 msg 分配的堆外内存。
当 Entry 对象被取消后,就需要减少 ChannelOutboundBuffer 的内存占用总量的水位线 totalPendingSize。
当更新后的水位线低于低水位线 DEFAULT_LOW_WATER_MARK = 32 * 1024 时,就将当前 channel 设置为可写状态。
当 Channel 的状态是第一次从不可写状态变为可写状态时,Netty 会在 pipeline 中再次触发 ChannelWritabilityChanged 事件的传播。
响应channelWritabilityChanged事件.png
4.2.2 发送数据前的最后检查---flush0
flush0 方法这里主要做的事情就是检查当 channel 的状态是否正常,如果 channel 状态一切正常,则调用 doWrite 方法发送数据。
•outboundBuffer == null || outboundBuffer.isEmpty() :如果 channel 已经关闭了或者对应写缓冲区中没有任何数据,那么就停止发送流程,直接 return。
•!isActive() :如果当前channel处于非活跃状态,则需要调用 outboundBuffer#failFlushed 通知 ChannelOutboundBuffer 中所有待发送操作对应的 channelPromise 向用户线程报告发送失败。并将待发送数据 Entry 对象从 ChannelOutboundBuffer 中删除,并释放待发送数据空间,回收 Entry 对象实例。
还记得我们在?《Netty如何高效接收网络连接》一文中提到过的 NioSocketChannel 的 active 状态有哪些条件吗??
NioSocketChannel 处于 active 状态的条件必须是当前 NioSocketChannel 是 open 的同时处于 connected 状态。
•!isActive() && isOpen():说明当前 channel 处于 disConnected 状态,这时通知给用户 channelPromise 的异常类型为 NotYetConnectedException ,并释放所有待发送数据占用的堆外内存,如果此时内存占用量低于低水位线,则设置 channel 为可写状态,并触发 channelWritabilityChanged 事件。
当 channel 处于 disConnected 状态时,用户可以进行 write 操作但不能进行 flush 操作。
•!isActive() && !isOpen() :说明当前 channel 处于关闭状态,这时通知给用户 channelPromise 的异常类型为 newClosedChannelException ,因为 channel 已经关闭,所以这里并不会触发 channelWritabilityChanged 事件。
•当 channel 的这些异常状态校验通过之后,则调用 doWrite 方法将 ChannelOutboundBuffer 中的待发送数据写进底层 Socket 中。
4.2.2.1 ChannelOutboundBuffer#failFlushed
该方法用于在 Netty 在发送数据的时候,如果发现当前 channel 处于非活跃状态,则将 ChannelOutboundBuffer 中 flushedEntry 与tailEntry 之间的 Entry 对象节点全部删除,并释放发送数据占用的内存空间,同时回收 Entry 对象实例。
4.2.2.2 ChannelOutboundBuffer#remove0
当一个 Entry 节点需要从 ChannelOutboundBuffer 中清除时,Netty 需要释放该 Entry 节点中包裹的发送数据 msg 所占用的内存空间。并标记对应的 promise 为失败同时通知对应的 listener ,由于 msg 得到释放,所以需要降低 channelOutboundBuffer 中的内存占用水位线,并根据 boolean notifyWritability 决定是否触发 ChannelWritabilityChanged 事件。最后需要将该 Entry 实例回收至 Recycler 对象池中。
