ChannelInBoundHandler入站处理器介绍
当对端数据入站到Netty通道时,Netty将触发流水线Pipeline上开启入站操作处理,触发入站处理器ChannelInboundHandler多对应的入站API。
入站处理器有两个接口,一个是超级接口ChannelHander,另一个是ChannelInboundHandler,
ChannelInboundHandler接口继承了超级接口ChannelHandler,如下图所示:
ChannelInboundHandler的常用方法如下图所示:
其核心源码如下:
public interface ChannelInboundHandler extends ChannelHandler {
/**
* The {@link Channel} of the {@link ChannelHandlerContext} was registered with its {@link EventLoop}
* 当通道注册完成后,Netty会调用fireChannelRegistered()方法,
* 触发通道注册事件,而在通道流水线注册过的入站处理器的channelRegistered()回调方法会被调用。
*/
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
/**
* The {@link Channel} of the {@link ChannelHandlerContext} was unregistered from its {@link EventLoop}
*/
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
/**
* The {@link Channel} of the {@link ChannelHandlerContext} is now active
* 当通道激活完成后,Netty会调用fireChannelActive()方法,
* 触发通道激活事件,而在通道流水线注册过的入站处理器的channelActive()回调方法会被调用。
* 使用channelActive实践场景:
* (1)监控处理器-任务队列堆积任务数监控
* (2)心跳处理器:在通道被激活时,开始发送心跳
*/
void channelActive(ChannelHandlerContext ctx) throws Exception;
/**
* The {@link Channel} of the {@link ChannelHandlerContext} was registered is now inactive and reached its
* end of lifetime.
* 当连接被断开或者不可用时,Netty会调用fireChannelInactive()方法,
* 触发连接不可用事件,而在通道流水线注册过的入站处理器的channelInactive()回调方法会被调用。
*/
void channelInactive(ChannelHandlerContext ctx) throws Exception;
/**
* Invoked when the current {@link Channel} has read a message from the peer.
* 当通道缓冲区可读时,Netty会调用fireChannelRead()方法,
* 触发通道可读事件,而在通道流水线注册过的入站处理器的channelRead()回调方法会被调用,
* 以便完成入站数据的读取和处理。
*/
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
/**
* Invoked when the last message read by the current read operation has been consumed by
* {@link #channelRead(ChannelHandlerContext, Object)}. If {@link ChannelOption#AUTO_READ} is off, no further
* attempt to read an inbound data from the current {@link Channel} will be made until
* {@link ChannelHandlerContext#read()} is called.
*当通道缓冲区读完时,Netty会调用fireChannelReadComplete()方法
* 触发通道缓冲区读完事件,而在通道流水线注册过的入站处理器的channelReadComplete()回调方法会被调用。
*
*/
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called if a {@link Throwable} was thrown.
* 当通道处理过程发生异常时,Netty会调用fireExceptionCaught()方法,
* 触发异常捕获事件,而在通道流水线注册过的入站处理器的exceptionCaught()方法会被调用。
*/
@Override
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
EmbeddedChannel 介绍
在netty的实际开发中, 底层通信传输的基础工作Netty已经完成,更多的工作是设计和开发ChannelHandler业务处理器,ChannelHandler处理器开发完成后, 需要投入单元测试。
单元测试的流程是需要将Handler业务处理器加入到通道的Pipeline 流水线中;接下来先后启动Netty服务器、客户端程序; 相互发送消息,测试业务处理器的效果。若开发一个ChannelHandler业务处理器都进行服务端和客户端的重复启动, 这个过程就是重复的、低效的且繁琐的过程,
Netty 提供了一个专用通道EmbeddedChannal 嵌入式通道来解决徒劳的、低效的重复启动客户端和服务器的工作。EmbeddedChannel仅仅是模拟入站与出站的操作,底层不进行实际的传输,不需要启动Netty服务和客户端, 除了不进行传输之外,EmbeddedChannel 的其他事件机制和处理流程与真正的传输通道是一样的,EmbeddedChannel提供了一组专门的方法模拟数据的发送和接收,因此EmbeddedChannel 可以在单元测试用例中方便、快速地进行ChannelHandler业务处理器的单元测试。
使用EmbeddedChannel进行单元测试的数据流向如下图所示:
入站数据使用 writeInbound()方法将消息写到 Channel 中,并通过 ChannelPipeline 沿 着入站的方向传递。使用 readInbound()方法来读取已被处理过的消息,以确 定结果是否和预期一样。
出站数据使用 writeOutbound()方法将消息写到 Channel 中,并通过 ChannelPipeline 沿 着出站的方向传递。使用 readOutbound()方法来读取已被处理过的消息,以确 定结果是否和预期一样。
ChannelInBoundHandler入站处理器的生命周期
在了解了ChannelInBoundHandler的源码后,为了更好地使用ChannelInBoundHandler入站处理器, 就需要弄清楚ChannelInBoundHandler入站处理器的各个方法的执行顺序和生命周期。 定义了一个入站处理器InHandlerDemo,继承ChannelInboundHandlerAdapter适配器,它实现了基类的大部分入站处理方法,并在每一个方法的实现代码中都加上必要的输出信息。 以便观察方法执行情况。
InHandlerDemo的示例代码如下:
@Slf4j
public class InHandlerDemo extends ChannelInboundHandlerAdapter {
/**
*:当业务处理器被加入到流水线后,此方法将被回调。也就是在完成 ch.pipeline().addLast(handler) 语句之后,
* 会回调 handlerAdded() 。
* @param ctx 通道处理器上下文,可以获取通道channel和通道处理流水线 channelPipeline
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:handlerAdded");
super.handlerAdded(ctx);
}
/**
* 通道注册,当通道注册完成后,会调用fireChannelRegistered方法触发通道注册事件,
* channelRegistered():当通道成功绑定一个 NioEventLoop 反应器后,此方法将被回调。
* @param ctx 通道处理器上下文,可以获取通道channel和通道处理流水线 channelPipeline
* @throws Exception
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:channelRegistered");
super.channelRegistered(ctx);
}
/**
* 通道激活, 当通道激活完成后,调用fireChannelActive方法激活通道激活事件,
* channelActive():当通道激活成功后,此方法将被回调。通道激活成功指的是,所有的业务处理器添加、
* 注册的异步任务完成,并且与 NioEventLoop 反应器绑定的异步任务完成。
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:channelActive");
super.channelActive(ctx);
}
/**
* 当通道缓冲区可读,Netty 的反应器完成数据读取后, 会调用 fireChannelRead 发射读取到
* 的二进制数据 。而在通道流水线注册过的入站处理器的 channelRead 回调方法,会被调用到,
* 以便完成入站数据的读取和处理。
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Logger.info("被调用:channelRead");
super.channelRead(ctx, msg);
}
/**
* 当通道缓冲区读完,Netty 会调用 fireChannelReadComplete ,触发通道缓冲区读完事件。
* 而在通道流水线注册过的入站处理器的 channelReadComplete 回调方法,会被调用到。
* 表示数据读取完毕
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:channelReadComplete");
super.channelReadComplete(ctx);
}
/**
* 当连接被断开或者不可用时,Netty 会调用 fireChannelInactive ,触发连接不可用事件。
* channelInactive():当通道的底层连接已经不是 ESTABLISH 状态,或者底层连接已
* 经关闭时,会首先回调所有业务处理器的 channelInactive() 方法。
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:channelInactive");
super.channelInactive(ctx);
}
/**
* 当通道处理过程发生异常时,
* Netty 会调用 fireExceptionCaught ,触发异常捕获事件。
*
* 通道和 NioEventLoop 反应器解除绑定,移除掉对这条通道
* 的事件处理之后,回调所有业务处理器的 channelUnregistered () 方法。
* @param ctx
* @throws Exception
*/
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:channelUnregistered");
super.channelUnregistered(ctx);
}
/**
* Netty 会移除掉通道上所有的业务处理器,并且回调所有业务处理器的HandlerRemoved()方法
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Logger.info("被调用:handlerRemoved");
super.handlerRemoved(ctx);
}
}
将InHandlerDemo入站处理器加入到嵌入式通道EmbeddedChannel的流水线中,然后,通过writeInbound方法写入ByteBuf数据包, InHandler作为一个入站处理器,会处理到流水线上的入站报文, 测试代码如下:
@Test
public void testInHandlerLifeCircle(){
final InHandlerDemo inHandler = new InHandlerDemo();
//初始化处理器
ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
@Override
protected void initChannel(EmbeddedChannel embeddedChannel) throws Exception {
embeddedChannel.pipeline().addLast(inHandler);
}
};
//创建嵌入式通道
EmbeddedChannel channel = new EmbeddedChannel(i);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//模拟入站,向嵌入式通道写一个入站数据包
channel.writeInbound(buf);
channel.flush();
//通道关闭
channel.close();
}
其运行结果如下:
从运行输出结果可以看到ChannelHandler中回调方法执行顺序如下图所示:
其中channelRead->channelReadComplete属于数据传输的入站回调过程。其他的就是入站处理器的周期方法。
Pipeline入站处理流程
一条Netty 通道需要很多的Handler业务处理器来处理业务。每条通道内部都有一条流水线(Pipeline )将 Handler 装配起来。 Netty 的业务处理器流水线 ChannelPipeline 是基于责任链设计模式( Chain of Responsibility)来设计的,内部是一个双向链表结构,能够支持动态地添加和删除Handler业务处理器。
建立3个入站处理器,分别是SimpleInHandlerA 、 SimpleInHandlerB 、 SimpleInHandlerC 。在 ChannelInitializer初始化处理器的 initChannel 方法中,把它们加入到流水线 中。添加的顺序为 A-> B -> C 。
示例代码如下:
static class SimpleInHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Logger.info("入站处理器 A: 被回调 ");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Logger.info("入站处理器 A: 被回调 ");
// super.channelReadComplete(ctx);
ctx.fireChannelReadComplete(); //入站操作的传播
}
}
static class SimpleInHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Logger.info("入站处理器 B: 被回调 ");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Logger.info("入站处理器 B: 被回调 ");
ctx.fireChannelReadComplete();//入站操作的传播
}
}
static class SimpleInHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Logger.info("入站处理器 C: 被回调 ");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
Logger.info("入站处理器 C: 被回调 ");
ctx.fireChannelReadComplete();//入站操作的传播
}
}
在以上三个内部入站处理器的channelRead(…)方法中,我们打印当前Handler业务处理器的信息,然后调用父类的channelRead()方法,而父类的channelRead()方法的作用,主要是把当前入站处理器中处理完毕的结果传递到下一个入站处理器,在示例程序中传递的对象都是同一个数据(也就是程序中的msg实例)。
测试代码如下:
@Test
public void testPipelineInBound() {
ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
ch.pipeline().addLast(new SimpleInHandlerA());
ch.pipeline().addLast(new SimpleInHandlerB());
ch.pipeline().addLast(new SimpleInHandlerC());
}
};
EmbeddedChannel channel = new EmbeddedChannel(i);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//向通道写一个入站报文
channel.writeInbound(buf);
ThreadUtil.sleepSeconds(Integer.MAX_VALUE);
}
运行结果如下:
从运行结果我们可以看到,入站处理器的流动次序是从前往后。加在前面的执行也在前面。如下图所示: