先说结论:
-
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”字符串:
3、我们再用telnet发送“ctx.channel.close.B”字符串:
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()