类图
DefaultChannelPipeline是一个Channel中用于处理各种入,出事件的流水线。
DefaultChannelPipeline内部通过双向链表维护了一个pipeline中可以处理事件的处理器列表。每个处理器都是一个AbstractChannelHandlerContext的子类。AbstractChannelHandlerContext中有两个链表相关的属性,prev和next,分别代表该节点的前后相邻节点。
而AbstractChannelHandlerContext有三个子类。分别是HeadContext,TailContext,DefaultChannelHandlerContext。
1、HeadContext,就是链表的表头。
HeadContext持有一个unsafe对象,unsafe对象用于处理各种入管道事件,比如从socket读取内容等。
2、TailContext,链表的表尾
3、DefaultChannelHandlerContext。链表除了两端节点外的中间节点都是DefaultChannelHandlerContext;该类有一个重要的属性是handler。也就是用户自定义的handler。
也就是说用户通过定义handler,之后添加handler到pipeline就可以增加自定义的数据处理逻辑啦。
示意图
上图是DefaultChannelPipeline中HandlerContext的链表关系示意,而每个handler能处理的能力,是handler决定的,实现ChannelInboundHandler和ChannelOutboundHandler接口,代表可以处理channel的in和out事件。按需实现即可。
read场景
NioEventLoop的SelectKey有可读事件的时候。通过unsafe.read()去读取数据。
NioEventLoop.java
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
...
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
...
}
unsafe是一个NioSocketChannelUnsafe对象。最终会调用pipeline的读数据方法
@Override
public final void read() {
...
final ChannelPipeline pipeline = pipeline();
...
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
byteBuf = allocHandle.allocate(allocator);
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
...
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
...
}
}
}
这段核心代码,主要的功能,就是申请一个长度为1024的ByteBuf。从channel中读取字节,存储到ByteBuf中。之后调用channel的pipeline对象对byteBuf进行读取。
代码如下:
DefaultChannelPipeline.java
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
可以看到fireChannelRead方法,代理了一下AbstractChannelHandlerContext.invokeChannelRead(head, msg);
方法,第一个参数是head
。也就是pipeline中的链表表头,HeadContext对象。
进一步看invokeChannelRead方法。
AbstractChannelHandlerContext.java
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
EventExecutor executor = next.executor();
这句是从HeadContext拿到所属Channel,进一步从Channel中获取该Channel所属的NioEventLoop。
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
这一坨代码,源码中常见的套路。也就是先检查NioEventLoop的任务已经运行了,如果运行了,则直接调用HeadContext的inivokeChannelRead方法。
如果没有运行,则启动线程把NioEventLoop的任务跑起来。并且将 HeadContext的inivokeChannelRead作为Task添加到NioEventLoop的任务队列里面。异步运行。
重点看下invokeChannelRead方法
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
((ChannelInboundHandler) handler()).channelRead(this, msg);
这句就是调用HeadContext的ChannelRead方法。之所以能强制转换成ChannelInBoundHandler,是因为HeadContext实现了InBound和OutBound接口。
HeadContext.java 中实现的ChannelInBoundHandler的channelRead方法。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.fireChannelRead(msg);
}
ctx.fireChannelRead,因为HeadContext继承了AbstractChannelHandlerContext方法。fireChannelRead方法,是AbstractChannelHandlerContext的方法实现。看源码如下:
AbstractChannelHandlerContext.java
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
return this;
}
这个函数分解来看。
findContextInbound(MASK_CHANNEL_READ)
方法实现如下:
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while ((ctx.executionMask & mask) == 0);
return ctx;
}
也就是从HeadContext开始逐步往链表尾部方向遍历,找到InBound的AbstractChannelHandlerContext。然后通过再调用invokeChannelRead。
总结一下
当监听的Socket有可read的数据时,从Pipeline的HeadContext开始,递归找到下一个具备InBoundHandler的AbstractChannelHandlerContext。调用其handler的channelRead方法。