netty-daxin-2 (netty の一般的なイベントの説明)

nettyのよくあるイベントの説明

ChannelHandlerInterface

プロセッサ インターフェイスは、IO イベントの処理、IO 操作のインターセプト、およびパイプライン内の次のプロセッサへの呼び出しの受け渡しに使用されます。

その下には 2 つのサブインターフェイスがあり、それらはすべて対応するアダプター クラスを持ち、また、受信 IO イベントと送信 IO 操作を処理するための複合 ChannelDuplexHandler クラスもあります。

  • ChannelInboundHandler: 受信 IO イベントの処理に使用されます
  • ChannelOutboundHandler: アウトバウンド IO 操作を処理するために使用されます。

コンテキストオブジェクト

  • ChannelHandler は ChannelHandlerContext コンテキスト オブジェクトにバインドされており、channelHandler はバインド先のコンテキスト オブジェクトを通じてパイプラインと対話する必要があります。
  • コンテキスト オブジェクトを使用することにより、ChannelHandler は上流または下流のプロセッサにイベントを渡したり、パイプラインを動的に変更したり、AttributeKey を使用してハンドラー自体のデータを保存したりできます。

ステータス管理

  • ChannelHandler は多くの場合、何らかのステータス情報を保存する必要がありますが、最も簡単な方法は、ハンドラー クラスで独自のメンバー変数を定義することです。
  • ただし、ハンドラーにはメンバー変数があるため、マルチスレッドの同時実行の問題を避けるために、接続ごとに新しいハンドラー インスタンスを作成する必要があります。

属性キーを使用する

  • メンバー変数を使用してハンドラー自体の状態データを保存することをお勧めしますが、何らかの理由により、接続ごとに新しいハンドラーを作成したくない場合もあります。この場合、ChannelHandlerContext によって提供される AttributeKey を使用してこの問題を解決できますが (以下に示すように)、現在、ChannelHandlerContext#attr および ChannelHandlerContext#hasAttr は非推奨となっており、Channel#attr (AttributeKey) および Channel#hasAttr を使用することをお勧めします。これにより、同じハンドラー インスタンスを複数のパイプラインで使用できるようになります。

    public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
          
          
    
        private final AttributeKey<Boolean> auth = AttributeKey.valueOf("auth");
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
          
          
    
            Attribute<Boolean> attr = ctx.attr(auth);
            if (msg instanceof LoginMessage) {
          
          
                authenticate((LoginMessage) msg);
                attr.set(true);
            } else if (message instanceof GetDataMessage) {
          
          
                if (Boolean.TRUE.equals(attr.get())) {
          
          
                    ctx.writeAndFlush(fetchSecret((GetDataMessage) msg));
                } else {
          
          
                    fail();
                }
            }
    
        }
    }
    

@Sharable アノテーション

  • ChannelHandler に @Sharable アノテーションが付けられている場合、ハンドラーを 1 回作成して別のパイプラインに追加するだけで、同時実行セキュリティの問題は発生しません。
  • ChannelHandler に @Sharable アノテーションが付けられていない場合、ハンドラーには状態があるため、ハンドラーがパイプラインに追加されるたびに新しいハンドラーを作成する必要があります。

ChannelHandlerAPI

  • ChannelHandler はトップレベルのインターフェイスとして多くの機能を備えておらず、次の 3 つの API のみを提供します。

    API 説明する
    ハンドラー追加() ChannelHandler が ChannelPipeline に追加されるときに呼び出されます。
    ハンドラー削除() ChannelHandler が ChannelPipeline から削除されるときに呼び出されます。
    例外をキャッチしました() ChannelHandler の処理中に例外が発生したときに呼び出されます。
  • ChannelHandler によって提供される API からは、ChannelHandler が Channel のデータ処理プロセスに直接関与していないことがわかりますが、ChannelPipeline チェーンと例外処理に応答するために使用されています。ChannelHandler のデータ処理は、そのサブインターフェイスによって処理されます。

public interface ChannelHandler {
    
    

    // 当ChannelHandler 添加到 ChannelPipeline 中时被调用
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    // 当 ChannelHandler 被从 ChannelPipeline 移除时调用
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

   
    // 当 ChannelHandler 在处理过程中出现异常时调用
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
    
    
        // no value
    }
}

ChannelHandlerアダプタークラス

public abstract class ChannelHandlerAdapter implements ChannelHandler {
    
    

   
    boolean added;
 
    protected void ensureNotSharable() {
    
    
        if (isSharable()) {
    
    
            throw new IllegalStateException("ChannelHandler " + getClass().getName() + " is not allowed to be shared");
        }
    }

   
    public boolean isSharable() {
    
    
       
        Class<?> clazz = getClass();
        Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
        Boolean sharable = cache.get(clazz);
        if (sharable == null) {
    
    
            sharable = clazz.isAnnotationPresent(Sharable.class);
            cache.put(clazz, sharable);
        }
        return sharable;
    }


    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    
    
        // NOOP
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
    
        // NOOP
    }

  
    @Skip
    @Override
    @Deprecated
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        ctx.fireExceptionCaught(cause);
    }
}

ChannelInboundHandler サブインターフェース

public interface ChannelInboundHandler extends ChannelHandler {
    
    

    // Channel 被注册到EventLoop 时
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

    // Channel 从 EventLoop 中取消时
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    // Channel 处于活跃状态,可以读写时
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    // Channel 不再是活动状态且不再连接它的远程节点时
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    // Channel 读取数据时
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    // Channel 从上一个读操作完成时
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    // ChannelInboundHandler.fireUserEventTriggered()方法被调用时
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    // Channel 的可写状态发生改变时
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
チャンネルステータス

出典:Da Ming の Netty コラム - Precision Data Craftsman: Netty ChannelHandler の謎を探る

チャネルはステートフルであり、次のように、チャネルの現在のステータスを確認するための API も提供します。

  • isOpen(): チャネルが開いているかどうかを確認します。
  • isRegistered(): チャンネルが登録されているかどうかを確認します。
  • isActive(): チャネルがアクティブかどうかを確認します。
    上記の 3 つの API は、チャネルの 4 つの状態に対応します。
説明する
チャンネル未登録 チャネルは作成されましたが、まだ EventLoop に登録されていません。この場合、isOpen() は true を返しますが、isRegistered() は false を返します。
チャンネル登録済み EventLoopにチャンネルが登録されました。この場合、isRegistered() は true を返しますが、isActive() は false を返します。
チャンネルアクティブ チャネルはすでにアクティブであり、データを送受信できます。このとき、isActive() は true を返します。
チャンネル非アクティブ チャネルがリモート ノードに接続されていません

ステータスの変化は次のとおりです。
ここに画像の説明を挿入します
Channel のステータスが変化すると、対応するイベントが生成され、これらのイベントが ChannelHandler に転送され、対応するメソッドが ChannelHandler に存在します。それに応答する ChannelHandler。 ChannelHandler で、このライフサイクルに関連するいくつかの API (channelRegistered()、channelUnregistered()、channelActive()、channelInactive() など) を定義します。これらの API については、Brother Daming が後で詳しく紹介します。

通話時間

呼び出しタイミングは以下の通りです。
ここに画像の説明を挿入します

ChannelHandler のライフサイクルの例
NetServer&InboundHandler のカスタマイズ
@Slf4j
public class NettyServer11 {
    
    

    public static void main(String[] args) {
    
    
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(16);

        ServerBootstrap serverBootstrap = new ServerBootstrap()
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                    @Override
                    protected void initChannel(SocketChannel ch) {
    
    
                        ch.pipeline().addLast(new CustomizeInboundHandler());
                    }
                });

        try {
    
    
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8080)).sync();

            log.info("=======服务器启动成功=======");

            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
    
    
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}

@Slf4j
public class CustomizeInboundHandler implements ChannelInboundHandler {
    
    

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【handlerAdded】- handler 添加到 ChannelPipeline");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelRegistered】- handler 注册到 eventLoop");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelActive】- Channel 准备就绪");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        log.info("【channelRead】- Channel 中有可读数据");
        if (msg instanceof ByteBuf) {
    
    
            try {
    
    
                ByteBuf byteBuf = (ByteBuf) msg;
                String code = byteBuf.toString(StandardCharsets.UTF_8);
                if ("evt".equals(code)) {
    
    
                    ctx.fireUserEventTriggered("JUST A EVT~");
                } else if ("ex".equals(code)) {
    
    
                    throw new NullPointerException("NULL POINTER~");
                } else if ("write".equals(code)) {
    
    
                    ByteBuf buf = ByteBufAllocator.DEFAULT.buffer().writeBytes("Great!Well Done~".getBytes());
                    ctx.channel().writeAndFlush(buf);
                } else {
    
    
                    log.info("服务端收到客户端发送的消息: {}", code);
                }
            } finally {
    
    
                ReferenceCountUtil.release(msg);
            }

        } else {
    
    
            ctx.fireChannelRead(msg);
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelReadComplete】- Channel 读取数据完成");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelInactive】- Channel 被关闭,不在活跃");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelUnregistered】- Channel 从 EventLoop 中被取消");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【handlerRemoved】- handler 从 ChannelPipeline 中移除");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        log.info("【exceptionCaught】 - ChannelHandler处理发生异常");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    
        log.info("【userEventTriggered】 - 激发自定义事件: {}", evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("【channelWritabilityChanged】 - 可写状态改变");
    }


}
Nettyクライアント
@Slf4j
public class NettyClient11 {
    
    

    public static void main(String[] args) throws Exception {
    
    

        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        Channel channel = new Bootstrap()
                .group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
    
    
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
    
    
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
    
    
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
                                if (msg instanceof ByteBuf) {
    
    
                                    ByteBuf byteBuf = (ByteBuf) msg;
                                    log.info("客户端收到服务端发送的消息: {}", byteBuf.toString(StandardCharsets.UTF_8));
                                    ReferenceCountUtil.release(msg);
                                }
                            }
                        });
                    }
                })
                .connect("127.0.0.1", 8080)
                .sync()
                .channel();

        log.info("=======客户端连接服务器成功=======");

        Scanner sc = new Scanner(System.in);

        while (true) {
    
    

            System.out.print("输入:");

            String line = sc.nextLine();

            if (line == null || line.length() == 0) {
    
    
                continue;
            }

            if ("close".equals(line)) {
    
    
                channel.close().sync();
                eventLoopGroup.shutdownGracefully();
                break;
            }

            // 输入内容
            ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
            byteBuf.writeBytes(line.getBytes());

            channel.writeAndFlush(byteBuf);

            System.out.println("=======发送成功=======");
        }

    }

}

テスト

クライアントは文字列 evt、ex、write、halo、close を順番に送信し、ログ出力を観察します
サーバー ログ

[15:57:54] [main] com.zzhua.test11.NettyServer11 [33] - =======服务器启动成功=======
[15:58:01] [nioEventLoopGroup-3-1] 【handlerAdded】- handler 添加到 ChannelPipeline
[15:58:01] [nioEventLoopGroup-3-1] 【channelRegistered】- handler 注册到 eventLoop
[15:58:01] [nioEventLoopGroup-3-1] 【channelActive】- Channel 准备就绪

[15:58:11] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:11] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:22] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:22] [nioEventLoopGroup-3-1] 【exceptionCaught】 - ChannelHandler处理发生异常
[15:58:22] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:31] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:31] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:58:46] [nioEventLoopGroup-3-1] 【channelRead】- Channel 中有可读数据
[15:58:46] [nioEventLoopGroup-3-1]  服务端收到客户端发送的消息: halo
[15:58:46] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成

[15:59:01] [nioEventLoopGroup-3-1] 【channelReadComplete】- Channel 读取数据完成
[15:59:01] [nioEventLoopGroup-3-1] 【channelInactive】- Channel 被关闭,不在活跃
[15:59:01] [nioEventLoopGroup-3-1] 【channelUnregistered】- Channel 从 EventLoop 中被取消
[15:59:01] [nioEventLoopGroup-3-1] 【handlerRemoved】- handler 从 ChannelPipeline 中移除

クライアントログ

[15:58:01]  [main] com.zzhua.test11.NettyClient11 [48] - =======客户端连接服务器成功=======
输入:evt
=======发送成功=======
输入:ex
=======发送成功=======
输入:write
=======发送成功=======
输入:[15:58:31] [INFO ] [nioEventLoopGroup-2-1] com.zzhua.test11.NettyClient11 [37] - 客户端收到服务端发送的消息: Great!Well Done~
halo
=======发送成功=======
输入:close
Disconnected from the target VM, address: '127.0.0.1:65133', transport: 'socket'
分析する

実装プロセス

  • サーバーは、クライアントが接続を開始したことを検出した後、処理されるハンドラーを ChannelPipeline に追加し、チャネルを EventLoop に登録します。登録が完了すると、チャネルは準備ができてアクティブになり、メッセージを受信できるようになります。
  • クライアントはサーバーにメッセージを送信し、サーバーはメッセージを読み取ります
  • クライアントが接続を閉じたことをサーバーが検出すると、チャネルは閉じられ、アクティブではなくなり、チャネルは EventLoop からキャンセルされ、ハンドラーは ChannelPipeline から削除されます。

ライフサイクル全体を通じて、応答メソッドの実行シーケンスは次のとおりです。

  • 建立连接:handlerAdded() -> channelRegistered() -> channelActive ()
  • 数据请求:channelRead() -> channelReadComplete()
  • 关闭连接:channelReadComplete() -> channelInactive() -> channelUnregistered() -> handlerRemoved()

ここで、Brother Daming は ChannelHandler のライフサイクル メソッドを要約しています。

  • handlerAdded(): ChannelHandler がパイプラインに追加されるときにトリガーされます。サーバーが新しいリンクを検出すると、ChannelHandler を双方向リンク リストに構築します (次の記事で説明します)。このメソッドは、ChannelHandler ビジネス処理チェーンが現在のチャネルに追加されたことを示すためにトリガーされます。
  • channelRegistered(): EventLoop に Channel が登録されるとトリガーされます。このメソッドがトリガーされ、現在のチャネルが EventLoop にバインドされていることを示します。
  • channelActive(): チャネル接続の準備ができたときにトリガーされます。このメソッドはトリガーされ、現在のチャネルがすでにアクティブであり、データの読み取りと書き込みができることを示します。
  • channelRead(): チャネルに読み取るデータがあるときにトリガーされます。このメソッドは、クライアントがサーバーにデータを送信するときにトリガーされます。このメソッドが呼び出されたときは、読み取るデータがあることを意味します。ビジネス ハンドラーをカスタマイズするときは、常にこのメソッドをオーバーライドします。
  • channelReadComplete(): チャネルデータが読み取られたときにトリガーされます。このメソッドは、サーバーがデータを読み取るたびにトリガーされ、データが読み取られたことを示します。
  • channelInactive(): チャンネルが切断されたときにトリガーされます。このメソッドはトリガーされ、チャネルがアクティブでなくなり、接続が閉じられたことを示します。
  • channelUnregistered(): チャネルが登録されていないときにトリガーされます: 接続が閉じられた後、チャネルと EventLoop の間のバインド関係をキャンセルする必要があります。
  • handlerRemoved(): ChannelHandler が ChannelPipeline から削除されるとトリガーされます。チャネルにバインドされている ChannelPipeline 内の ChannelHandler ビジネス処理チェーンをすべて削除します。
ChannelInboundHandlerAdapter アダプター クラス
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
    
    

    @Skip
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelRegistered();
    }


    @Skip
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelUnregistered();
    }


    @Skip
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelActive();
    }


    @Skip
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelInactive();
    }


    @Skip
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        ctx.fireChannelRead(msg);
    }


    @Skip
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelReadComplete();
    }


    @Skip
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    
        ctx.fireUserEventTriggered(evt);
    }


    @Skip
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.fireChannelWritabilityChanged();
    }


    @Skip
    @Override
    @SuppressWarnings("deprecation")
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
    
    
        ctx.fireExceptionCaught(cause);
    }
}

SimpleChannelInboundHandler の単純な実装
  • これは、ChannelInboundHandler インターフェイスの実装に役立ちます。使用する場合は、それを継承するだけで済みます。
  • その一般的な表現は次のことを意味します。
    • 渡されたメッセージがジェネリックで指定された型であることを読み取ると、メッセージを channelRead0 メソッドに渡し、実装のためにサブクラスに渡します。呼び出しが完了した後、メッセージを解放するのに役立ちます。
    • 渡されたメッセージがジェネリックで指定されたタイプでない場合、メッセージは次の受信プロセッサに直接渡されますが、この時点ではメッセージを解放することはできません。
  • 利点: 指定されたジェネリック タイプのメッセージのみを処理するため、異なるタイプのメッセージを処理するためのコードをすべて同じクラスに入れる必要がなくなります。
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
    
    

    private final TypeParameterMatcher matcher;
    
    private final boolean autoRelease;


    protected SimpleChannelInboundHandler() {
    
    
        this(true);
    }


    protected SimpleChannelInboundHandler(boolean autoRelease) {
    
    
        matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
        this.autoRelease = autoRelease;
    }

 
    protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType) {
    
    
        this(inboundMessageType, true);
    }


    protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {
    
    
        matcher = TypeParameterMatcher.get(inboundMessageType);
        this.autoRelease = autoRelease;
    }

    public boolean acceptInboundMessage(Object msg) throws Exception {
    
    
        return matcher.match(msg);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        boolean release = true;
        try {
    
    
            if (acceptInboundMessage(msg)) {
    
    
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                channelRead0(ctx, imsg);
            } else {
    
    
                release = false;
                ctx.fireChannelRead(msg);
            }
        } finally {
    
    
            if (autoRelease && release) {
    
    
                ReferenceCountUtil.release(msg);
            }
        }
    }

    protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
}

ChannelInboundHandlerAdapter を使用する場合、プールされた ByteBuf インスタンスに関連するメモリを明示的に解放する必要があることに注意してください。Netty はこの目的のための特別なメソッドを提供しますReferenceCountUtil.release()。 need このメソッドは、次のように ChannelInboundHandler チェーンの最後で使用してメモリを解放する必要があります。

public class ByteBufReleaseHandler extends ChannelInboundHandlerAdapter{
    
    
  @Override
  public void channelRead(ChannelHandlerContext ctx,Object msg){
    
    
    //释放msg
    ReferenceCountUtil.release(msg);
  }
}

しかし、友人の中にはこれを忘れて無用なトラブルを引き起こす人もいます。 Netty は、このプロセスを簡素化するのに役立つクラス SimpleChannelInboundHandler を提供します。ビジネス処理クラスの場合、ChannelInboundHandlerAdapter の代わりに SimpleChannelInboundHandler を継承することで解決できます。

SimpleChannelInboundHandlerを使うと明示的にリソースを解放する必要がなくなり、非常に使いやすいのではないでしょうか?

ChannelOutboundHandler サブインターフェース

public interface ChannelOutboundHandler extends ChannelHandler {
    
    
    
    // 请求将 Channel 绑定到本地地址时
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 请求将 Channel 连接到远程节点时
    void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                 SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 请求将 Channel 从远程节点断开时
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求关闭 Channel 时
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求将 Channel 从它的 EventLoop 注销时
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

    // 请求从 Channel 中读取数据时
    void read(ChannelHandlerContext ctx) throws Exception;

    // 请求通过 Channel 将入队数据刷入远程节点时
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    // 请求通过 Channel 将数据写入远程节点时
    void flush(ChannelHandlerContext ctx) throws Exception;
}

ChannelOutboundHandlerAdapter アダプター クラス
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
    
    

   
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
            ChannelPromise promise) throws Exception {
    
    
        ctx.bind(localAddress, promise);
    }

    
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception {
    
    
        ctx.connect(remoteAddress, localAddress, promise);
    }

    
    @Skip
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
    
    
        ctx.disconnect(promise);
    }

   
    @Skip
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
    
    
        ctx.close(promise);
    }

    
    @Skip
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    
    
        ctx.deregister(promise);
    }

    
    @Skip
    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.read();
    }

   
    @Skip
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    
    
        ctx.write(msg, promise);
    }

   
    @Skip
    @Override
    public void flush(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.flush();
    }
}

ChannelDuplexHandler 複合クラス

ChannelInboundHandlerAdapter から継承し、ChannelOutboundHandler インターフェイスを実装します。

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {
    
    

    
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
                     ChannelPromise promise) throws Exception {
    
    
        ctx.bind(localAddress, promise);
    }

    
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
                        SocketAddress localAddress, ChannelPromise promise) throws Exception {
    
    
        ctx.connect(remoteAddress, localAddress, promise);
    }

    
    @Skip
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
    
    
        ctx.disconnect(promise);
    }

   
    @Skip
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    
    
        ctx.close(promise);
    }

    
    @Skip
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    
    
        ctx.deregister(promise);
    }

    
    @Skip
    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.read();
    }

   
    @Skip
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    
    
        ctx.write(msg, promise);
    }

    
    @Skip
    @Override
    public void flush(ChannelHandlerContext ctx) throws Exception {
    
    
        ctx.flush();
    }
}

おすすめ

転載: blog.csdn.net/qq_16992475/article/details/135038855