一文搞懂Netty发送数据全流程 | 你想知道的细节全在这里(六)

lemonvita
发布于 2022-7-18 17:33
浏览
0收藏

4. flush


从前面 Netty 对 write 事件的处理过程中,我们可以看到当用户调用 ctx.write(msg) 方法之后,Netty 只是将用户要发送的数据临时写到 channel 对应的待发送缓冲队列 ChannelOutboundBuffer 中,然而并不会将数据写入 Socket 中。

 

而当一次 read 事件完成之后,我们会调用 ctx.flush() 方法将 ChannelOutboundBuffer 中的待发送数据写入 Socket 中的发送缓冲区中,从而将数据发送出去。

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //本次OP_READ事件处理完毕
        ctx.flush();
    }

}

4.1 flush事件的传播

一文搞懂Netty发送数据全流程 | 你想知道的细节全在这里(六)-鸿蒙开发者社区 pipeline结构.png


flush 事件和 write 事件一样都是 oubound 事件,所以它们的传播方向都是从后往前在 pipeline 中传播。

 

触发 flush 事件传播的同样也有两个方法:

 

 •channelHandlerContext.flush():flush事件会从当前 channelHandler 开始在 pipeline 中向前传播直到 headContext。
 

channelHandlerContext.channel().flush():flush 事件会从 pipeline 的尾结点 tailContext 处开始向前传播直到 headContext。

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {

    @Override
    public ChannelHandlerContext flush() {
        //向前查找覆盖flush方法的Outbound类型的ChannelHandler
        final AbstractChannelHandlerContext next = findContextOutbound(MASK_FLUSH);
        //获取执行ChannelHandler的executor,在初始化pipeline的时候设置,默认为Reactor线程
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeFlush();
        } else {
            Tasks tasks = next.invokeTasks;
            if (tasks == null) {
                next.invokeTasks = tasks = new Tasks(next);
            }
            safeExecute(executor, tasks.invokeFlushTask, channel().voidPromise(), null, false);
        }

        return this;
    }

}

这里的逻辑和 write 事件传播的逻辑基本一样,也是首先通过findContextOutbound(MASK_FLUSH)  方法从当前 ChannelHandler 开始从 pipeline 中向前查找出第一个 ChannelOutboundHandler 类型的并且实现 flush 事件回调方法的 ChannelHandler 。注意这里传入的执行资格掩码为 MASK_FLUSH。

 

执行ChannelHandler中事件回调方法的线程必须是通过pipeline#addLast(EventExecutorGroup group, ChannelHandler... handlers)为 ChannelHandler 指定的 executor。如果不指定,默认的 executor 为 channel 绑定的  reactor 线程。

 

如果当前线程不是 ChannelHandler 指定的 executor,则需要将 invokeFlush() 方法的调用封装成 Task 交给指定的 executor 执行。

 

4.1.1 触发nextChannelHandler的flush方法回调

private void invokeFlush() {
        if (invokeHandler()) {
            invokeFlush0();
        } else {
            //如果该ChannelHandler并没有加入到pipeline中则继续向前传递flush事件
            flush();
        }
    }

这里和 write 事件的相关处理一样,首先也是需要调用 invokeHandler() 方法来判断这个 nextChannelHandler 是否在 pipeline 中被正确的初始化。

 

如果 nextChannelHandler 中的 handlerAdded 方法并没有被回调过,那么这里就只能跳过 nextChannelHandler,并调用 ChannelHandlerContext#flush 方法继续向前传播flush事件。

 

如果 nextChannelHandler 中的 handlerAdded 方法已经被回调过,说明 nextChannelHandler 在 pipeline 中已经被正确的初始化好,则直接调用nextChannelHandler 的 flush 事件回调方法。

    private void invokeFlush0() {
        try {
            ((ChannelOutboundHandler) handler()).flush(this);
        } catch (Throwable t) {
            invokeExceptionCaught(t);
        }
    }

这里有一点和 write 事件处理不同的是,当调用 nextChannelHandler 的 flush 回调出现异常的时候,会触发 nextChannelHandler 的 exceptionCaught 回调。

 

 private void invokeExceptionCaught(final Throwable cause) {
        if (invokeHandler()) {
            try {
                handler().exceptionCaught(this, cause);
            } catch (Throwable error) {
                if (logger.isDebugEnabled()) {
                    logger.debug(....相关日志打印......);
                } else if (logger.isWarnEnabled()) {
                    logger.warn(...相关日志打印......));
                }
            }
        } else {
            fireExceptionCaught(cause);
        }
    }

而其他 outbound 类事件比如 write 事件在传播的过程中发生异常,只是回调通知相关的 ChannelFuture。并不会触发 exceptionCaught 事件的传播。

标签
已于2022-7-18 17:33:24修改
收藏
回复
举报
回复
    相关推荐