Netty ChannelHandler组件
ChannelHandler
ChannelHandler概念
在Reactor反应器模型中,反应器查询到I/O事件后,分发到Handler业务处理器,由Handler完成I/O操作和业务处理;
ChannelHandler是一个接口,负责对I/O事件或者I/O操作进行拦截和处理,它可以选择性地拦截和处理注册的事件,也可以透传和终止事件,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序(Handler);
基于ChannelHandler接口,用户可以方便地进行业务逻辑定制,例如打印日志,统一封装异常信息,性能统计和消息编码等;
ChannelHandler继承体系图
ChannelHandler继承体系图如下:
Netty 定义了下面两个重要的ChannelHandler子接口:
- ChannelInboundHandler处理入站数据以及各种状态变化;
- ChannelOutboundHandler处理出站数据并且允许拦截所有的操作;
入站处理触发的方向:Netty的内部(如通道)到ChannelInboundHandler入站处理器;
出站处理触发的方向:从ChannelOutboundHandler出站处理器到Netty的内部(如通道);
ChannelHandler的生命周期
ChannelHandler的生命周期方法
方法 | 描述 |
void handlerAdded(ChannelHandlerContext ctx) throws Exception; |
当把ChannelHandler添加到ChannelPipeline中时被调用; |
void handlerRemoved(ChannelHandlerContext ctx) throws Exception; |
当从ChannelPipeline中移除ChannelHandler时被调用; |
@Deprecated |
当处理过程中在ChannelPipeline 中有错误产生时被调用; |
ChannelInboundHandler
ChannelInboundHandler用于处理入站I/O事件,处理输入数据和Channel状态类型改变,对于适配器有ChannelInboundHandlerAdapter;
当对端数据入站到Netty通道时,Netty将触发入站处理器ChannelInboundHandler所对应的入站API,进行入站操作处理;
ChannelInboundHandler的API
方法 | 描述 |
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
|
当通道注册完成后,Netty会调用fireChannelRegistered方法,触发通道注册事件,在 通道流水线注册过的入站处理器Handler的channelRegistered回调方法将会被调用到; |
void channelActive(ChannelHandlerContext ctx) throws Exception;
|
当通道激活完成后,Netty会调用fireChannelActive方法触发通道激活事件,在通道流水线注册过的入站处理器的channelActive回调方法会被调用到; |
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
|
当通道缓冲区可读,Netty的反应器完成数据读取后,Netty会调用fireChannelRead方法传递读取到的二进制数据,在通道流水线注册过的入站处理器的channelRead回调方法会被调用到,以便完成入站数据的读取和处理; |
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
|
当通道缓冲区读完,Netty会调用fireChannelReadComplete方法触发通道缓冲区读完事件,在通道流水线注册过的入站处理器的channelReadComplete回调方法会被调用到; |
void channelInactive(ChannelHandlerContext ctx) throws Exception;
|
当连接被断开或者不可用时,Netty会调用fireChannelInactive方法触发连接不可用事件,在通道流水线注册过的入站处理器的channelInactive回调方法会被调用到; |
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
|
当通道处理过程发生异常时,Netty会调用fireExceptionCaught触发异常捕获事件,在通道流水线注 册过的入站处理器的 exceptionCaught方法会被调用到; 注:这个方法是在通道处理器中ChannelHandler定义的方法,入站处理器、出站处理器接口都继承到了该方法; |
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
|
当Channel 的可写状态发生改变时被调用,用户可以确保写操作不会完成得太快(以避免发生OutOfMemoryError)或者可以在Channel变为再次可写时恢复写入,可通过调用Channel的isWritable方法来检测Channel 的可写性;与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置; |
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
|
当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用,因为一个POJO被传经了ChannelPipeline; |
ChannelInboundHandler生命周期
测试如下:
查看代码
public class InHandlerDemo extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(InHandlerDemo.class);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
logger.info("handlerAdded被调用");
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
logger.info("handlerRemoved被调用");
super.handlerRemoved(ctx);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
logger.info("channelRegistered被调用");
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
logger.info("channelUnregistered被调用");
super.channelUnregistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelActive被调用");
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("channelInactive被调用");
super.channelInactive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("channelRead被调用");
super.channelRead(ctx, msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.info("channelReadComplete被调用");
super.channelReadComplete(ctx);
}
@Test
public void testInHandlerLifeCircle() {
final InHandlerDemo inHandler = new InHandlerDemo();
// 初始化处理器
ChannelInitializer initializer = new ChannelInitializer<EmbeddedChannel>() {
@Override
protected void initChannel(EmbeddedChannel ch) throws Exception {
ch.pipeline().addLast(inHandler);
}
};
// 创建嵌入式通道
EmbeddedChannel embeddedChannel = new EmbeddedChannel(initializer);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
// 模拟入站,向嵌入式通道写一个入站数据包
embeddedChannel.writeInbound(buf);
embeddedChannel.flush();
// 模拟入站,再写一个入站数据包
embeddedChannel.writeInbound(buf);
embeddedChannel.flush();
// 通道关闭
embeddedChannel.close();
}
}
执行结果如下:
从输出的结果可以看到, ChannelInboundHandler中的回调方法的执行顺序为:
handlerAdded -> channelRegistered -> channelActive -> 数据传输的入站回调 -> channelInactive -> channelUnregistered -> handlerRemoved
其中,数据传输的入站回调过程为:
channelRead -> channelReadComplete
入站处理器中的回调方法:
- channelRead
有数据包入站,通道可读;流水线会启动入站处理流程,从前向后,入站处理器的 channelRead()方法会被依次回调到;
- channelReadComplete
流水线完成入站处理后会从前向后,依次回调每个入站处理器的 channelReadComplete方法,表示数据读取完毕;
读数据的入站回调过程会根据入站数据的数量被重复调用,每一次有ByteBuf数据包入站都会调用到;除了两个入站回调方法外,其余的6个方法都和ChannelHandler的生命周期有关;
ChannelOutboundHandler
ChannelOutboundHandler用于处理出站I/O事件,处理输出数据,并且允许拦截所有的操作,对于适配器有ChannelOutboundHandlerAdapter;
当业务处理完成后,需要操作Java NIO底层通道时,通过一系列的ChannelOutboundHandler出站处理器,完成Netty通道到底层通道的操作;
ChannelOutboundHandler的API
方法 | 描述 |
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
|
监听地址(IP+端口)绑定:完成底层Java I/O通道的 IP地址绑定;如果使用TCP传输协议,这个方法用于服务器端; |
void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception;
|
连接服务端:完成底层Java I/O通道的服务器端的连接操作;如果使用TCP传输协议,这个方法用于客户端; |
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
当请求将Channel 从远程节点断开时被调用; |
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
|
写数据到底层:完成Netty通道向底层Java I/O通道的数据写入操作;此方法仅仅是触发一下操作而已,并不是完成实际的数据写入操作; |
void flush(ChannelHandlerContext ctx) throws Exception;
|
将底层缓存区的数据腾空,立即写出到对端; |
void read(ChannelHandlerContext ctx) throws Exception;
|
出站处理的read操作是启动数据读取,或者说开始数据的读取操作,不是实际的数据读取,只有入站处理的read操作才真正执行底层读数据; 入站read处理在完成Netty通道从Java I/O通道的数据读取后,再把数据发射到通道的pipeline最后数据会依次进入pipeline的各个入站处理器最终被入站处理器的channelRead方法处理; |
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
断开服务器连接:断开底层Java I/O通道的 socket连接;如果使用 TCP传输协议,此方法主要用于客户端; |
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
主动关闭通道:关闭底层的通道,如服务器端的新连接监听通道; |
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
|
当请求将Channel从它的EventLoop注销时被调用; |
ChannelOutboundHandler生命周期
测试如下:
查看代码
public class OutHandlerDemo extends ChannelOutboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(OutHandlerDemo.class);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
logger.info("handlerAdded被调用");
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
logger.info("handlerRemoved被调用");
super.handlerRemoved(ctx);
}
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
logger.info("bind被调用");
super.bind(ctx, localAddress, promise);
}
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
logger.info("connect被调用");
super.connect(ctx, remoteAddress, localAddress, promise);
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
logger.info("disconnect被调用");
super.disconnect(ctx, promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
logger.info("read被调用");
super.read(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
logger.info("write被调用");
super.write(ctx, msg, promise);
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
logger.info("flush被调用");
super.flush(ctx);
}
@Test
public void testOutHandlerLifeCircle() {
final OutHandlerDemo outHandler = new OutHandlerDemo();
ChannelInitializer initializer = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(outHandler);
}
};
EmbeddedChannel channel = new EmbeddedChannel(initializer);
//测试出站写入
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
ChannelFuture f1 = channel.pipeline().writeAndFlush(buf);
ChannelFuture f2 = channel.pipeline().writeAndFlush(buf);
CountDownLatch countDownLatch = new CountDownLatch(2);
f1.addListener((future) -> {
if (future.isSuccess()) {
logger.info("write is finished");
}
countDownLatch.countDown();
});
f2.addListener((future) -> {
if (future.isSuccess()) {
logger.info("write is finished");
}
countDownLatch.countDown();
});
try {
channel.close();
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果如下:
从输出的结果可以看到, ChannelOutboundHandler中的回调方法的执行顺序为:
handlerAdded -> 数据传输的出站回调 -> handlerRemoved
其中,数据传输的出站回调过程为:
read -> write -> flush
ChannelInitializer通道初始化处理器
一条Netty的通道拥有一条Handler业务处理器流水线pipeline,pipeline负责装配自己的Handler业务处理器,而pipeline装配Handler的工作是发生在通道开始工作之前;
在引导的过程中调用了handler或者childHandler方法来添加单个的ChannelHandler,这对于简单的应用程序来说可能已经足够了,但是它不能满足更加复杂的需求,如一个必须要支持多种协议的应用程序将会有很多的ChannelHandler,而不会是一个庞大而又笨重的类;
开发者可以根据需要,通过在ChannelPipeline 中将它们链接在一起来部署尽可能多的ChannelHandler;
如果在引导的过程中开发者只能设置一个ChannelHandler,那应该如何处理?
Netty提供了了一个特殊的ChannelInboundHandlerAdapter子类ChannelInitializer,使用ChannelInitializer可向流水线中装配业务处理器;
io.netty.channel.ChannelInitializer
ChannelInitializer中initChannel方法是一个抽象方法,需要开发人员自己实现;
在通道初始化时会调用提前注册的初始化处理器的initChannel方法,如在父通道接收到新连接并且要初始化其子通道时会调用初始化器的initChannel方法,并且会将新接收的通道作为参数,传递给此方法;
一般来说,initChannel方法的大致业务代码是:拿到新连接通道作为实际参数,往它的流水线中装配 Handler业务处理器;
ChannelHandler适配器
有一些适配器类可以将编写自定义的ChannelHandler 所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现;下面这些是编写自定义ChannelHandler时经常会用到的适配器类:
- ChannelHandlerAdapter
- ChannelInboundHandlerAdapter
- ChannelOutboundHandlerAdapter
- ChannelDuplexHandler
其中,ChannelDuplexHandler可用于处理入站和出站事件;
对于大多数ChannelHandler会选择性地拦截和处理某个或者某些事件,其他的事件会忽略,由下一个ChannelHandler进行拦截和处理;这样会导致一个问题,用户ChannelHandler必须要实现ChannelHandler的所有接口,包括它不关心的那些事件处理的接口,这会导致用户代码的臃肿,可维护性变差;为了解决这个问题,Netty提供了ChannelHandlerAdapter基类,它的所有接口实现都是事件透传的,如果用户ChannelHandler关心某个事件,只需要覆盖ChannelHandlerAdapter对应的方法即可,对于不关心的方法,用户可以直接继承使用父类的方法,子类的代码可以变得简洁和清晰;
ChannelHandler支持的注解
@ChannelHandler.Sharable注解
io.netty.channel.ChannelHandler.Sharable
从注释可以看出@ChannelHandler.Sharable注解的作用是标注一个 ChannelHandler实例可以被多个通道安全地共享,另外如果没有使用该注解修饰的ChannelHandler,那该ChannelHandler每次添加到通道的流水线都必须创建一个新的Handler实例;
Sharable的意思是在多个通道的流水线可以加入同一个ChannelHandler业务处理器实例,而这种共享操作, Netty默认是不允许的;
换句话说,如果一个Handler没有使用@ChannelHandler.Sharable注解修饰,并试图将同一个Handler实例添加到多个ChannelPipeline通道流水线,或在同一个ChannelPipeline通道流水线中添加同一个Handler实例时, Netty将会抛出异常;
注:使用@ChannelHandler.Sharable注解修饰ChannelHandler实例需要保证线程安全;
但在很多应用场景需要ChannelHandler业务处理器实例能共享;例如,一个服务器处理十万以上的通道,如果一个通道都新建很多重复的ChannelHandler实例,那就需要上十万以上重复的Handler实例,这就会浪费很多空间,降低了服务器的性能;因此,如果Handler实例没有与特定通道强相关的数据或者状态,建议设计成共享的模式;
如何判断一个Handler是否为@ChannelHandler.Sharable共享?
io.netty.channel.ChannelHandlerAdapter#isSharable
该方法的作用:如果其对应的实现被标注为Sharable, 那么这个方法将返回true, 表示它可以被添加到多个ChannelPipeline中;
测试案例
以上面的OutHandlerDemo为例,对其进行测试;
- OutHandlerDemo未使用@ChannelHandler.Sharable注解修饰
- 单个ChannelPipeline,同一个ChannelHandler实例
查看代码
@Test
public void testSharable1() {
OutHandlerDemo outHandlerDemo=new OutHandlerDemo();
ChannelInitializer initializer = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(outHandlerDemo);
ch.pipeline().addLast(outHandlerDemo);
}
};
EmbeddedChannel channel = new EmbeddedChannel(initializer);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//向通道写一个入站报文
channel.writeInbound(buf);
channel.close();
}
执行结果如下:
-
- 单个ChannelPipeline,非同一个ChannelHandler实例
查看代码
@Test
public void testSharable2() {
OutHandlerDemo outHandlerDemo=new OutHandlerDemo();
ChannelInitializer initializer = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(outHandlerDemo);
ch.pipeline().addLast(new OutHandlerDemo());
}
};
EmbeddedChannel channel = new EmbeddedChannel(initializer);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//向通道写一个入站报文
channel.writeInbound(buf);
channel.close();
}
执行结果如下:
-
- 多个ChannelPipeline,同一个ChannelHandler实例
查看代码
@Test
public void testSharable3() {
OutHandlerDemo outHandlerDemo=new OutHandlerDemo();
ChannelInitializer initializer = new ChannelInitializer<EmbeddedChannel>() {
protected void initChannel(EmbeddedChannel ch) {
ch.pipeline().addLast(outHandlerDemo);
ch.pipeline().addLast(outHandlerDemo);
}
};
EmbeddedChannel channel1 = new EmbeddedChannel(initializer);
ByteBuf buf1 = Unpooled.buffer();
buf1.writeInt(1);
//向通道写一个入站报文
channel1.writeInbound(buf1);
channel1.close();
System.out.println(" 再来一个 channel" );
EmbeddedChannel channel2 = new EmbeddedChannel(initializer);
ByteBuf buf2 = Unpooled.buffer();
buf2.writeInt(1);
//向通道写一个入站报文
channel2.writeInbound(buf2);
channel2.close();
}
执行结果如下:
- OutHandlerDemo使用@ChannelHandler.Sharable注解修饰
- 单个ChannelPipeline,同一个ChannelHandler实例,执行testSharable1方法
执行结果如下:
-
- 多个ChannelPipeline,同一个ChannelHandler实例,执行testShare3方法
执行结果如下: