一文彻底弄懂ctx.close()和ctx.channel().close()的区别

先说结论:

  • ctx.close():只会总当前处理器BHandler出发,向前寻找出站Handler,并调用它们的close()方法;

  • ctx.channel().close():将从整个pipeline的tail尾部向前寻找所有的出站Handler,并调用它们的close()方法!


一、编写测试代码:

1、需要引入的依赖:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.70.Final</version>
</dependency>

2、编写一个双向处理器ZidanHandler:

/**
 * ChannelDuplexHandler:双向处理器:
 *    既继承了ChannelInboundHandlerAdapter类,
 *    又实现了ChannelOutboundHandler接口。
 * @Author jiguiquan
 * @Email jiguiquan@haier.com
 * @Data 2023-07-12 9:31
 */
public class ZidanHandler extends ChannelDuplexHandler {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(ctx.name() + " channelRead: " + msg);
        String result = (String) msg;
        
        if (("ctx.close." + ctx.name()).equals(result)) {
            ctx.close();
        } else if (("ctx.channel.close." + ctx.name()).equals(result)) {
            ctx.channel().close();
        } else {
            ctx.fireChannelRead(msg);
        }
    }

    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        System.out.println(ctx.name() + " close");
        ctx.close(promise);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println(ctx.name() + " write");
        ctx.write(String.format("[%s]%s", ctx.name(), msg), promise);
    }
}

3、主入口类NettyServer:

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(3);

        new ServerBootstrap()
                .channel(NioServerSocketChannel.class)
                .group(boss, worker)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                                .addLast("decoder", new StringDecoder())
                                .addLast("encoder", new StringEncoder())
                                .addLast("A", new ZidanHandler())
                                .addLast("B", new ZidanHandler())
                                .addLast("C", new ZidanHandler());
                    }
                }).bind(8090).sync();
    }
}

二、启动项目,进行测试看现象

1、项目启动后,我们使用本地的telnet命令作为客户端进行交互:

C:\Users\admin>telnet localhost 8090

# 重点,进入telnet窗口后,我们需要按下 Ctrl + ]键
欢迎使用 Microsoft Telnet Client

Escape 字符为 'CTRL+]'

Microsoft Telnet>

2、我们先用telnet发送“ctx.close.B”字符串:

1689127803575030.png

3、我们再用telnet发送“ctx.channel.close.B”字符串:

1689127898845898.png

4、根据现象得出结论:

  • ctx.close():只会总当前处理器BHandler出发,向前寻找出站Handler,并调用它们的close()方法;

  • ctx.channel().close():将从整个pipeline的tail尾部向前寻找所有的出站Handler,并调用它们的close()方法!


三、通过源码弄明白为什么

1、ctx.close() 的源码:

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
    public ChannelFuture close() {
        return this.close(this.newPromise());
    }
    
    public ChannelFuture close(final ChannelPromise promise) {
        if (this.isNotValidPromise(promise, false)) {
            return promise;
        } else {
        
            // 获取下一个出站Handler(出站是向前,所以实际是前一个)
            final AbstractChannelHandlerContext next = this.findContextOutbound(4096);
            EventExecutor executor = next.executor();
            if (executor.inEventLoop()) {
                next.invokeClose(promise);
            } else {
                safeExecute(executor, new Runnable() {
                    public void run() {
                        next.invokeClose(promise);
                    }
                }, promise, (Object)null, false);
            }
    
            return promise;
        }
    }
    
    private AbstractChannelHandlerContext findContextOutbound(int mask) {
        AbstractChannelHandlerContext ctx = this;
        EventExecutor currentExecutor = this.executor();
    
        do {
            // 找出当前handlerContext的prev前一个handlerContext
            // 但是会跳过不是Outbound的那些handler
            ctx = ctx.prev;
        } while(skipContext(ctx, currentExecutor, mask, 130560));
    
        return ctx;
    }
}

2、ctx.channel().close() 的源码:

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
    public ChannelFuture close() {
        return this.pipeline.close();
    }
    
    public final ChannelFuture close() {
        // 其实到这里已经很清晰了,从当前整个pipeline中的tail尾节点开始,向前寻找出站handler,
        // 并执行他们的close()方法
        return this.tail.close();
    }
}

四、总结:

1、那么我们何时使用ctx.close(),又何时使用ctx.channel().close()方法呢:

  • 如果你正写一个 ChannelHandler, 并且在处理逻辑时,想在这个 handler 中关闭 channel, 则直接调用 ctx.close();

  • 如果你正准备从一个外部的 handler (例如, 你有一个后台的非I/O线程, 并且你想从该线程中关闭连接),那么就可以调用ctx.Channel.close());

    但是这种规则也是非绝对的!

2、其他类似的方法对:

  • ctx.write() 与 ctx.channel().write()

  • ctx.writeAndFlush() 与 ctx.channel().writeAndFlush()

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐