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 的正常的生命周期流转也如上表(从上到下),当状态发生改变时,将会生成对应的事件,并转发给 ChannelPipeline 的 ChannelHandler 处理。
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),参考下文:
使用 ChannelInboundHandler 注意事项
直接覆盖 ChannelInboundHandlerAdapter 的 channelRead 需要显示释放内存,代码如下:
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) {
// 这里不需要显示释放资源
}
}
为什么使用这个方法呢?在 SimpleChannelInboundHandler 的 channelRead 方法会调用 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 |
关于 ChannelPromise 与 ChannelFuture :ChannelPromise 是 ChannelFuture 的一个子类,其主要用法是添加一个监听器,便于确认操作是否成功。因为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 且听下回分解。
参考资料
-
《netty in action》
-
掘金小册《Netty 入门与实战:仿写微信 IM 即时通讯系统》: juejin.cn/book/684473…
小册Github: github.com/lightningMa…
本文由 发给官兵 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出 处。如转载至微信公众号,请在文末添加作者公众号二维码。
