inbound 事件和 outbound 事件的流向是不一样的, inbound 事件的流行是从下至上, 而 outbound 刚好相反, 是从上到下. 并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT() 方法, 而 outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT() 方法.
例如 ChannelHandlerContext.fireChannelRegistered() 调用会发送一个 ChannelRegistered 的 inbound 给下一个ChannelHandlerContext, 而 ChannelHandlerContext.bind 调用会发送一个 bind 的 outbound 事件给 下一个 ChannelHandlerContext.
Inbound 事件传播方法有:
ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()
Oubound 事件传输方法有:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
注意, 如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
public class MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
}
public clas MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
System.out.println("Closing ..");
ctx.close(promise);
}
}
PipeLine中事件传递
Outbound 事件都是请求事件(request event), 即请求某件事情的发生, 然后通过 Outbound 事件进行通知.
Outbound 事件的传播方向是 tail -> customContext -> head.
链接请求
Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect
这样形成一个链条处理
Inbound 事件是一个通知事件, 即某件事已经发生了, 然后通过 Inbound 事件进行通知. Inbound 通常发生在 Channel 的状态的改变或 IO 事件就绪.
Context.fireChannelActive -> Connect.findContextInbound -> nextContext.invokeChannelActive -> nextHandler.channelActive -> nextContext.fireChannelActive
总结
对于 Outbound事件:
Outbound 事件是请求事件(由 Connect 发起一个请求, 并最终由 unsafe 处理这个请求)
Outbound 事件的发起者是 Channel
Outbound 事件的处理者是 unsafe
Outbound 事件在 Pipeline 中的传输方向是 tail -> head.
在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.xxx (例如 ctx.connect) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
Outbound 事件流: Context.OUT_EVT -> Connect.findContextOutbound -> nextContext.invokeOUT_EVT -> nextHandler.OUT_EVT -> nextContext.OUT_EVT
对于 Inbound 事件:
Inbound 事件是通知事件, 当某件事情已经就绪后, 通知上层.
Inbound 事件发起者是 unsafe
Inbound 事件的处理者是 Channel, 如果用户没有实现自定义的处理方法, 那么Inbound 事件默认的处理者是 TailContext, 并且其处理方法是空实现.
Inbound 事件在 Pipeline 中传输方向是 head -> tail
在 ChannelHandler 中处理事件时, 如果这个 Handler 不是最后一个 Hnalder, 则需要调用 ctx.fireIN_EVT (例如 ctx.fireChannelActive) 将此事件继续传播下去. 如果不这样做, 那么此事件的传播会提前终止.
Outbound 事件流: Context.fireIN_EVT -> Connect.findContextInbound -> nextContext.invokeIN_EVT -> nextHandler.IN_EVT -> nextContext.fireIN_EVT
如何区分是In还是Out事件?
ch.pipeline().addLast→DefaultChannelHandlerContext newCtx =new DefaultChannelHandlerContext(this, invoker, name, handler);→ skipFlags = skipFlags(handler);
skipFlags
字段标识跳过/捕获那个事件。
5.0版本中不分开区分In/Out事件了,全部都继承ChannelHandlerAdapter,需要处理什么方法,重写什么方法就可以。
设置标识的skipFlags0
方法部分源码。
/**
* Determines the {@link #skipFlags} of the specified {@code handlerType} using the reflection API.
*/
private static int skipFlags0(Class extends ChannelHandler> handlerType) {
int flags = 0;
try {
if (handlerType.getMethod(
"handlerAdded", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_HANDLER_ADDED;
}
if (handlerType.getMethod(
"handlerRemoved", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_HANDLER_REMOVED;
}
if (handlerType.getMethod(
"exceptionCaught", ChannelHandlerContext.class, Throwable.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_EXCEPTION_CAUGHT;
}
if (handlerType.getMethod(
"channelRegistered", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_CHANNEL_REGISTERED;
}
if (handlerType.getMethod(
"channelActive", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_CHANNEL_ACTIVE;
}
if (handlerType.getMethod(
"channelInactive", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_CHANNEL_INACTIVE;
}
if (handlerType.getMethod(
"channelRead", ChannelHandlerContext.class, Object.class).isAnnotationPresent(Skip.class)) {
flags |= MASK_CHANNEL_READ;
}
.......
return flags;
}
在父类ChannelHandlerAdapter
中所有的方法都添加了@Skip注解,子类继承它,重写方法后,该方法的@Skip注解即失效。
handlerType.getMethod( "channelActive", ChannelHandlerContext.class).isAnnotationPresent(Skip.class)
方法如果有@Skip注解的话,将flags对应的位置1。即如果重写了的话对应位是0
最后有事件时fireChannelRead
等函数会调用
DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_READ);
不断循环,找到最近的一个Handler处理。
private DefaultChannelHandlerContext findContextInbound(int mask) {
DefaultChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while ((ctx.skipFlags & mask) != 0);
return ctx;
}
这个循环是当对应位是0时,停止循环。需要处理。这样处理一直到head/tail。