5 pipeline
5.1 pipeline的初始化
5.1.1 pipeline在创建Channel的时候被创建
AbstractChannel的构造方法中会进行创建DefaultChannelPipeline
即服务端和客户端channel被创建的时候创建
5.1.2 pipeline节点数据结构:ChannelHandlerContext接口
扩展自AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker
AttributeMap:可以存储自定义属性
ChannelInboundInvoker:可以传播读事件(注册/Active)
ChannelOutboundInvoker:可以传播写事件
- pipeline的串行结构如何实现?
依靠AbstractChannelHandlerContext的next、prev指针
5.1.3 Pipeline中的两大哨兵tail、head
在5.1.1中被初始化:
TailContext:会传播读事件,主要做一些收尾的事情,如异常处理
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler
HeadContext: 这里的Inbound会赋值为false,所以会传播写事件;
会进行事件的传播,每次进行读写事件传播都从HeadContext开始;
进行读写操作时,都会委托给Unsafe
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler
channelActive()方法会调用readIfIsAutoRead(),即连接刚建立成功之后,默认情况下自动注册读事件,底层对应的NioEventLoop的selector可以轮询到一个读事件
- 为什么TailContext和HeadContext会相反?
todo(位置不同,起到的作用不同)
5.2 添加删除ChannelHandler
5.2.1 添加
使用者:自定义handler一般继承ChannelOutboundHandlerAdapter或ChannelInboundHandlerAdapter
//ChannelInitializer最后会被移除
//作用:调用initChannel方法来添加自定义handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new XXXHandler());
}
});
流程:addlast()
1.判断是否重复添加(checkMultiplicity():直接通过(added标志位(而不是遍历)&是否可共享(@Sharable注解))来判断,减少时间复杂度(性能点))
2.创建节点(通过组合,把handler包装成ChannelHandlerContext接口默认实现类DefaultChannelHandlerContext)并添加至链表(tail的前一个)
3.回调添加完成事件
5.2.2 删除
- 场景:权限校验
public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf password) throws Exception {
if (paas(password)) {
ctx.pipeline().remove(this);
} else {
ctx.close();
}
}
private boolean paas(ByteBuf password) {
return false;
}
}
- 流程:remove为入口
1.找到节点
2.通过标准的链表删除来删除节点(默认情况下都会有头尾节点head、tail,所以不需要判断“是否为首或尾节点”)(head、tail是不可被删除的,由assert保护)
3.回调删除handler事件(callHandlerRemoved0(ctx))
5.3 事件和异常的传播
ChannelHandler接口继承关系:
5.3.1 Inbound事件的传播
5.3.1.1 何为inbound事件以及ChannelInbounHandler
inbound事件主要包括Register、Active、read等事件
ChannelInbounHandler:在ChannelHandler基础上添加了一些Register、Active、read等功能,被添加到pipeline后,通过instanceof识别出为inboundHandler,从而可以处理inbound事件
5.3.1.2 ChannelRead()事件的传播
//服务端msg其实是一个连接,客户端msg是bytebuf数据
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
按addLast的顺序依次传播
传播案例:
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("InBoundHandlerB: " + msg);
//此方法会调用下一个inbound
//没有这一行的话,则不会传播
ctx.fireChannelRead(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
//此方法会传播到channelRead,传入的参数就是msg
ctx.channel().pipeline().fireChannelRead("hello world");
}
}
pipeline的fireChannelRead会从head开始传播
ChannelInboundHandler的fireChannelRead则会传播给下一个inbound
bytebuf会在tailContext进行释放
5.3.1.3 SimpleInboundHandler处理器
- 作用
能够自动释放bytebuf;
因为可能某些handler没有向下传播,无法到达tailContext,无法释放,所以需要这个处理器
- 原理
传入bytebuf泛型,定义了抽象方法channelRead0,子类重写,在此方法上编写自己的清除逻辑,内部会调用此方法
5.3.2 Ounbound事件的传播
5.3.2.1 何为outbound事件以及ChannelOutbounHandler
outbound事件:写事件,更多是使用者使用的,主动,而inbound为被动
5.3.2.2 write()事件的传播
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception
按addLast的顺序逆序依次传播(pipeline为双向链表,从tail开始往前即可)
谁真正负责写操作?head中的unsafe
传播案例:
public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OutBoundHandlerB: " + msg);
ctx.write(msg, promise);//没有这一行的话,则不会传播
}
@Override
public void handlerAdded(final ChannelHandlerContext ctx) {
ctx.executor().schedule(() -> {
//都是向客户端写数据
//区别:
//第一行会回调上面的write方法,传入的参数会给msg参数;是从tail开始的
//第二行则会传播给上一个outhandler(通过findContextOutbound()来找到next);是从当前节点开始的,可能会错过一些outboundhandler
ctx.channel().write("hello world");
ctx.write("hello world");
}, 3, TimeUnit.SECONDS);
}
}
5.3.3 异常的传播
案例代码:
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
//BusinessException为自定义异常
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
throw new BusinessException("from InBoundHandlerB");
}
//AC只有此方法
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("InBoundHandlerB.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
//ABC相同
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("OutBoundHandlerC.exceptionCaught()");
ctx.fireExceptionCaught(cause);
}
}
public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// ..
if (cause instanceof BusinessException) {
System.out.println("BusinessException");
}
}
}
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new InBoundHandlerA());
ch.pipeline().addLast(new InBoundHandlerB());
ch.pipeline().addLast(new InBoundHandlerC());
ch.pipeline().addLast(new OutBoundHandlerA());
ch.pipeline().addLast(new OutBoundHandlerB());
ch.pipeline().addLast(new OutBoundHandlerC());
ch.pipeline().addLast(new ExceptionCaughtHandler());
}
});
结果:异常定义在B,A先执行channelRead过去了,所以不会执行exceptionCaught
5.3.3.1 异常的触发链
channelRead
中抛出的异常被catch捕捉,调用notifyHandlerException,会回调到exceptionCaught方法
;
然后使用ctx.fireExceptionCaught(cause)继续往next
传播
异常传播顺序:按addlast添加顺序(不管是inbound还是outbound),传播到tail节点,进行最终处理
5.3.3.2 异常处理的最佳实践
在childHandler方法(即pipeline链)的最后添加一个异常处理器,对每一种异常分别做处理,其他的handler一直使用ctx.fireExceptionCaught(cause)传播下去即可;
这样就不会被tail处理:tail会打印logger.warn级别的日志进行提醒
5.4 总结
- Netty是如何判断ChannelHandler类型的?
调用”pipeline添加节点“方法时,使用instanceof,通过两个boolean类型的字段inbound和outbound来设置类型
- 对于ChannelHandler的添加应该遵循什么样的顺序?
in会顺序执行、out会逆序执行,所以inbound正着来,outbound要反着来
- 用户手动触发事件传播,不同的触发方式有什么样的区别?
ctx.channel().pipeline().fireChannelRead()从head开始,ctx.fireChannelRead()从下一个开始(可能会错过一些inboundHandler)
ctx.channel().write()从tail开始, ctx.write()从下一个开始(可能会错过一些outboundHandler)