Netty实战(五):ChannelHandler

442 阅读3分钟
原文链接: fageiguanbing.gitee.io

ChannelHandler 和 ChannelPipeline 在 netty 中的作用

  无论是从服务端来看,还是客户端来看,在 Netty 整个框架里面,一条连接对应着一个 Channel,这条 Channel 所有的处理逻辑都在一个叫做 ChannelPipeline 的对象里面,ChannelPipeline 是一个双向链表结构,他和 Channel 之间是一对一的关系。

  ChannelPipeline 里面每个节点都是一个 ChannelHandlerContext 对象,这个对象能够拿到和 Channel 相关的所有的上下文信息,然后这个对象包着一个重要的对象,那就是逻辑处理器 ChannelHandler。接下来我们先看看Channel。

Channel的生命周期

  定义在 io.netty.channel.Channel 中,和 ChannelInboundHandler 有密切的联系。

状态 描述
ChannelRegistered channel已经被注册到了EventLoop
ChannelActive channel处于连接到远程节点的状态,它现在可以接受和发送数据了
ChannelInactive channel没有连接到远程节点
ChannelUnregistered channel已经创建,但还未注册到EventLoop

  Channel 的正常的生命周期流转也如上表(从上到下),当状态发生改变时,将会生成对应的事件,并转发给 ChannelPipelineChannelHandler 处理。

ChannelHandler 的生命周期

  定义在 io.netty.channel.ChannelHandler 中,每一个方法中都有一个 ChannelHandlerContext 参数。我们的业务逻辑是写在 ChannelHandler 的实现类中的。

类型 描述
handlerAdded 把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved 从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught 各个handler中发生异常时被调用

ChannelInboundHandler 接口

  ChannelHandler 子接口,主要处理入站数据以及各个状态变化。下表为这个接口的生命周期。已经在Channel列出的生命周期也是 ChannelInboundHandler 的生命周期的一部分,不再赘述。

类型 描述
channelRead 从channel中读取数据时被调用
channelReadComplete 从channel上一个读操作做完时被调用
channelWritabilityChanged 当channel的可写状态发生改变时被调用
userEventTriggered ChannelPipeLine.fireUserEventTriggered()方法被调用时被调用

关于可写状态(isWritable),参考下文:

www.cnblogs.com/yuyijq/p/44…

使用 ChannelInboundHandler 注意事项

  直接覆盖 ChannelInboundHandlerAdapterchannelRead 需要显示释放内存,代码如下:

public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // netty提供了显示释放内存的工具类
        ReferenceCountUtil.release(msg);
    }
}

  netty使用warn级别的日志消息记录未释放的资源。还有一个更加简单的使用方式是直接继承 SimpleChannelInboundHandler 并覆盖 channelRead0,完全不需要考虑资源的释放;代码如下:

public class DiscardHandler extends SimpleChannelInboundHandler {
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        // 这里不需要显示释放资源
    }
}

  为什么使用这个方法呢?在 SimpleChannelInboundHandlerchannelRead 方法会调用 channelRead0 方法,而且在 finally 代码块使用了 ReferenceCountUtil.release(); 方法来释放资源。

ChannelOutboundHandler 接口

  ChannelHandler 子接口,主要处理出栈数据并允许拦截所有操作。

类型 描述
bind(ChannelHandlerContext, SocketAddress, ChannelPromise) 当channel绑定到本地地址时被调用
connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise) 当channel连接到远程节点时被调用
disconnect(ChannelHandlerContext, ChannelPromise) 当channel从远程节点被断开时被调用
close(ChannelHandlerContext, ChannelPromise) 请求关闭channel时被调用
deregister(ChannelHandlerContext, ChannelPromise) 请求将channel从它的EventLoop注销时被调用
read(ChannelHandlerContext) 当从channel读取数据时调用
flush(ChannelHandlerContext) 把通过write方法写入buffer的数据冲刷到远程节点
write(ChannelHandlerContext, Object, ChannelPromise) 把数据写入bufffer

  关于 ChannelPromiseChannelFutureChannelPromiseChannelFuture 的一个子类,其主要用法是添加一个监听器,便于确认操作是否成功。因为netty所有操作都是异步的,所以netty提供了监听器用来异步回调。例如下面代码可以处理入栈出栈全局异常:

public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void write (ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        promise.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) {
                if (!future.isSuccess()) {
                    // 失败处理
                }
            }
        });
    }
}

关于 ChannelPipeline 且听下回分解。

参考资料

本文由 发给官兵 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出 处。如转载至微信公众号,请在文末添加作者公众号二维码。