本章介绍
- ChannelPipeline
- ChannelHandlerContext
- ChannelHandler
- Inbound vs outbound(入站和出站)
6.1 ChannelPipeline
- addFirst(...),添加ChannelHandler在ChannelPipeline的第一个位置
- addBefore(...),在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
- addAfter(...),在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
- addLast(ChannelHandler...),在ChannelPipeline的末尾添加ChannelHandler
- remove(...),删除ChannelPipeline中指定的ChannelHandler
- replace(...),替换ChannelPipeline中指定的ChannelHandler
ChannelPipeline pipeline = ch.pipeline();
FirstHandler firstHandler = new FirstHandler();
pipeline.addLast("handler1", firstHandler);
pipeline.addFirst("handler2", new SecondHandler());
pipeline.addLast("handler3", new ThirdHandler());
pipeline.remove("handler3");
pipeline.remove(firstHandler);
pipeline.replace("handler2", "handler4", new FourthHandler());
被添加到ChannelPipeline的ChannelHandler将通过IO-Thread处理事件,这意味了必须不能有其他的IO-Thread阻塞来影响IO的整体处理;有时候可能需要阻塞,例如JDBC。因此,Netty允许通过一个EventExecutorGroup到每一个ChannelPipeline.add*方法,自定义的事件会被包含在EventExecutorGroup中的EventExecutor来处理,默认的实现是DefaultEventExecutorGroup。6.2 ChannelHandlerContext
6.2.1 通知下一个ChannelHandler
- 调用Channel的方法
- 调用ChannelPipeline的方法
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//Event via Channel
Channel channel = ctx.channel();
channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
//Event via ChannelPipeline
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
}
});
}
下图表示通过Channel或ChannelPipeline的通知:可能你想从ChannelPipeline的指定位置开始,不想流经整个ChannelPipeline,如下情况:
- 为了节省开销,不感兴趣的ChannelHandler不让通过
- 排除一些ChannelHandler
// Get reference of ChannelHandlerContext
ChannelHandlerContext ctx = ..;
// Write buffer via ChannelHandlerContext
ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
该消息流经ChannelPipeline到下一个ChannelHandler,在这种情况下使用ChannelHandlerContext开始下一个ChannelHandler。下图显示了事件流:如上图显示的,从指定的ChannelHandlerContext开始,跳过前面所有的ChannelHandler,使用ChannelHandlerContext操作是常见的模式,最常用的是从ChannelHanlder调用操作,也可以在外部使用ChannelHandlerContext,因为这是线程安全的。
6.2.2 修改ChannelPipeline
public class WriteHandler extends ChannelHandlerAdapter {
private ChannelHandlerContext ctx;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
public void send(String msg){
ctx.write(msg);
}
}
请注意,ChannelHandler实例如果带有@Sharable注解则可以被添加到多个ChannelPipeline。也就是说单个ChannelHandler实例可以有多个ChannelHandlerContext,因此可以调用不同ChannelHandlerContext获取同一个ChannelHandler。如果添加不带@Sharable注解的ChannelHandler实例到多个ChannelPipeline则会抛出异常;使用@Sharable注解后的ChannelHandler必须在不同的线程和不同的通道上安全使用。怎么是不安全的使用?看下面代码:@Sharable
public class NotSharableHandler extends ChannelInboundHandlerAdapter {
private int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
count++;
System.out.println("channelRead(...) called the " + count + " time");
ctx.fireChannelRead(msg);
}
}
上面是一个带@Sharable注解的Handler,它被多个线程使用时,里面count是不安全的,会导致count值错误。为什么要共享ChannelHandler?使用@Sharable注解共享一个ChannelHandler在一些需求中还是有很好的作用的,如使用一个ChannelHandler来统计连接数或来处理一些全局数据等等。
6.3 状态模型
- channelUnregistered
- channelRegistered
- channelActive
- channelInactive
6.4 ChannelHandler和其子类
6.4.1 ChannelHandler中的方法
- handlerAdded,ChannelHandler添加到实际上下文中准备处理事件
- handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
- exceptionCaught,处理抛出的异常
6.4.2 ChannelInboundHandler
- channelRegistered,ChannelHandlerContext的Channel被注册到EventLoop;
- channelUnregistered,ChannelHandlerContext的Channel从EventLoop中注销
- channelActive,ChannelHandlerContext的Channel已激活
- channelInactive,ChannelHanderContxt的Channel结束生命周期
- channelRead,从当前Channel的对端读取消息
- channelReadComplete,消息读取完成后执行
- userEventTriggered,一个用户事件被处罚
- channelWritabilityChanged,改变通道的可写状态,可以使用Channel.isWritable()检查
- exceptionCaught,重写父类ChannelHandler的方法,处理异常
/**
* 实现ChannelInboundHandlerAdapter的Handler,不会自动释放接收的消息对象
* @author c.k
*
*/
public class DiscardHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//手动释放消息
ReferenceCountUtil.release(msg);
}
}
/**
* 继承SimpleChannelInboundHandler,会自动释放消息对象
* @author c.k
*
*/
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
//不需要手动释放
}
}
如果需要其他状态改变的通知,可以重写Handler的其他方法。通常自定义消息类型来解码字节,可以实现ChannelInboundHandler或ChannelInboundHandlerAdapter。有一个更好的解决方法,使用编解码器的框架可以很容的实现。使用ChannelInboundHandler、ChannelInboundHandlerAdapter、SimpleChannelInboundhandler这三个中的一个来处理接收消息,使用哪一个取决于需求;大多数时候使用SimpleChannelInboundHandler处理消息,使用ChannelInboundHandlerAdapter处理其他的“入站”事件或状态改变。ChannelInitializer用来初始化ChannelHandler,将自定义的各种ChannelHandler添加到ChannelPipeline中。
6.4.3 ChannelOutboundHandler
- bind,Channel绑定本地地址
- connect,Channel连接操作
- disconnect,Channel断开连接
- close,关闭Channel
- deregister,注销Channel
- read,读取消息,实际是截获ChannelHandlerContext.read()
- write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道
- flush,刷新消息到通道
public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ReferenceCountUtil.release(msg);
promise.setSuccess();
}
}
重要的是要记得释放致远并直通ChannelPromise,若ChannelPromise没有被通知可能会导致其中一个ChannelFutureListener不被通知去处理一个消息。如果消息被消费并且没有被传递到ChannelPipeline中的下一个ChannelOutboundHandler,那么就需要调用ReferenceCountUtil.release(message)来释放消息资源。一旦消息被传递到实际的通道,它会自动写入消息或在通道关闭是释放。