文章目录
在上篇文章中提到每个SocketCahnnel或者ServerSocketChannel的父类AbstractChannel的构造函数中会实例化DefaultChannelPipeline。在本文中会详细的介绍ChannelPiple实例化过程中的细节、以及ChannelPiple的工作原理。
1. ChannelPiple
每当 ServerSocketChannel 创建一个新的连接,就会创建一个 SocketChannel,对应的就是目标客户端。而每一个 SocketChannel(包括ServerSocketChannel)都将会分配一个全新的 ChannelPipeline,他们的关系是永久不变的;而每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext,他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler。关系图如下:
再来来看看DefaultChannelPipeline类的继承关系图:
看到DefaultChannelPipeline实现了 ChannelInboundInvoker及ChannelOutboundInvoker两个接口。顾名思义,一个是处理通道的inbound事件调用器,另一个是处理通道的outbound事件调用器。
- inbound: 本质上就是执行I/O线程将从外部read到的数据传递给业务线程的一个过程。
- outbound: 本质上就是业务线程将数据传递给I/O线程, 直至发送给外部的一个过程。
再回顾一下上篇文章中已经提到过的DefaultChannelPipeline的构造方法
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
在此构造函数中绑定了当前channel实例,然后初始化双向列表的头尾节点。其中head是HeadContext的实例,tail是TailContext的实例,HeadContext与TailContext都是DefaultChannelPipeline的内部类。下面看看它们的类的继承结构。
HeadContext类:
TailContext类:
从类继承图我们可以看出:
- HeadContext与TailContext都是通道的handler(中文一般叫做处理器)
- HeadContext既可以用于outbound过程的handler,也可以用于inbound过程的handler
- TailContext只可以用于inbound过程的handler
- HeadContext 与 TailContext 同时也是一个处理器上下文对象
下面继续进入到HeadContext的构造方法
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, false, true);
//获取channel中的unsafe对象
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
AbstractChannelHandlerContext的构造方法:
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
此构造方法,只是设置了当前context对象对应的Pipeline以及此context是作用于outbound。
最后一行代码setAddComplete设置当前节点的状态,通过sun.misc.Unsafe的CAS操作来完成的。
小结:
- 每个channel初始化时,都会创建一个与之对应的pipeline
- 此pipeline内部就是一个双向链表
- 双向链表的头结点是处理outbound过程的handler,尾节点是处理inbound过程的handler;
- 双向链表的结点同时还是handler上下文对象;
2. 添加初始化Handller
通过上一节 知道了ChannelPipeline 中含有两个 ChannelHandlerContext(同时也是 ChannelHandler), 我们知道ServerSocketChannel和SocketChannel都关联着PipeLine,当然后者是被前者创建的。在Netty中,ServerSocketChannel有两个特殊的Handler,一个是初始化Handler,另一个是处理Accept事件的Handler。
初始化Handler的在服务端启动过程中的创建了继承了ChannelInitializer的匿名类。源码后面看,先说个大概的作用,它的作用是添加用户自定义Handler和处理Accept事件的Handler。
这里说一下处理Accept事件的Handler,Netty中的实现叫做ServerBootStrapAcceptor,它的作用是就是处理Accept事件详细过程在后续Accept事件的文章中介绍。
但是这个 Pipeline是如何添加我们指定的handller呢?一般在我们服务端会有如下的代码:
//设置ServerSocketChannel关联的handler
.handler(new LoggingHandler(LogLevel.INFO))
//设置SocketChannel关联的handler
.childHandler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new Someonehandller());
}
如果看过ServerBootStrap源码分析Netty进阶篇:ServerBootStrap源码分析就知道这里只是设置了ServerBootStrap中的childHandller和AbstractBootStart中的handller属性,当然handler()不是必须的。那么这里设置的handler和何时添加到PipleLine中呢?
AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister->init(channel)
ServerBootStrap重写了init方法,比BootStartp实现稍微复杂一些。
@Override
void init(Channel channel) throws Exception {
//省略部分代码
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
//...
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = handler();
if (handler != null) {
//将ServerSocketChannel的Handler添加到pipeline
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
//将ServerBootstrapAcceptor添加到ServerSocketChannel的pipeline
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
}
}
这个方法此处传入的参数channel是NioServerSocketChannel,channel.pipeline()获取此channel关联的PipleLine,接下来是调用关联的pipeline的addLast()方法,是new了一个ChannelInitializer的匿名对象,下面看看ChannelInitializer的继承关系
通过类继承图,我们得知ChannelInitializer的匿名对象其实就是一个处理inbound过程的处理器。
addLast最终具体实现如下:
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
newContext方法的作用就是对传入的handler进行包装,最后返回一个绑定了handler的context对象,也就是DefaultChannelHandlerContext实例,形成和PipeLine中head和tail相似的格式,如何对这个Handler设置呢?看到源码后真的不得不叹服Netty开发者博大精深!
newContext方法中调用了下面构造方法:
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
内部调用了父类AbstractChannelHandlerContext构造方法,isInbound方法的返回值是布尔类型。
接下里看addlast0方法:
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
看到这里就很舒服了,这就是在尾节点前面插入一个节点的操作。
init方法就是给NioServerSocketChannel的pipeLine中添加一个匿名ChannelInitializer类,在该类的initChannel方法中添加自定义Handler和ServerBootStrapAcceptor。现在pipeLine中有三个Handler:Head+初始化Handler+Tail。
3. ServerSocketChannel添加Acceptor Handler和自定义Handler
那么有一个疑问,该匿名类的initChannel方法什么时候执行?也就是自定义Handler和ServerBootstrapAcceptor什么时候被添加到pipleLine中?
在ServerBootStarp源码分析中提到过,当channel注册到selector后的调用链为:
Bootstrap.initAndRegister ->
AbstractBootstrap.initAndRegister ->
MultithreadEventLoopGroup.register ->
SingleThreadEventLoop.register ->
AbstractUnsafe.register -> //交给EventLoop执行
AbstractUnsafe.register0 ->
------------------------------------------
private void register0(ChannelPromise promise) {
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
//注册ServerSocketChannel是交给EventLoop执行的,所以还没有绑定端口
//isActive()为false,所以不会执行下面内容。当Channel激活才会调用fireChannelActive。fireChannelActive的调用过程后面分析。
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
}
doRegister方法最后调用了AbstractAbstractNioChannel的doRegister,其内部是JDK NIO channel的register。我们主要看一下fireChannelRegistered,也就是注册时间在pipeline的传递逻辑
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
参数head 是一个 AbstractChannelHandlerContext 实例, 并且它没有重写 invokeChannelRegistered方法,因此调用的AbstractChannelHandlerContext中实现的方法。
@Override
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
handler() 返回的是和Context关联的handler,关联的操作是在addLast方法中的newContext,前面有讲到。Head实现了channelRegistered方法,但是他的内部没有真正的逻辑,只是继续调用ctx.fireChannelRegistered(),这里是AbstractChannelHandlerContext的实现,pipeline.fireChannelRegistered()是调用链的入口,但是这两个方法的内部差不多,只不过invokeChannelRegistered()方法的参数不一样,前者是findContextInbound(),显然这个参数是下一个InBoundhandler。
当遍历到初始化Handler对应的Context时, 并接着调用了 ChannelInitializer.channelRegistered 方法. 继续进入此方法
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
// the handler.
if (initChannel(ctx)) {
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
// miss an event.
ctx.pipeline().fireChannelRegistered();
} else {
// Called initChannel(...) before which is the expected behavior, so just forward the event.
ctx.fireChannelRegistered();
}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
// We do so to prevent multiple calls to initChannel(...).
exceptionCaught(ctx, cause);
} finally {
remove(ctx);
}
return true;
}
return false;
}
initChannel(C ch) 这个方法就是我们重写的方法,在此处调用, 当添加了自定义的 ServerBootstrapAcceptor 后, 会删除初始化Handler这个ChannelHandler, 即 remove(ctx),与此同时又会发出一个事件,又回到了第三节的开头。至于为什么要再次发出这个事件?官方的注释是:我们调用initChannel(…)所以我们需要现在调用pipeline.fireChannelRegistered()以确保我们不会错过任何事件。
因此最后的 Pipeline就只有:head+自定义handlller+ServerBootstrapAcceptor+tail 。ServerBootstrapAcceptord和我们在handler(xxxhandler)中设置的是同一个级别的,都是Inboundhandler,更多内容在Accept事件处理的文章中详细介绍。
最后以一张图总结添加ServerSocketChannel的handler过程。
4 . Connect事件传输机制
1.在客户端启动分析中当调用了 Bootstrap.connect 方法时,就会触发一个 Connect 请求事件,回顾一下调用过程,pipeline 的 connect 代码如下:
public final ChannelFuture connect(
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, localAddress, promise);
}
- connect 事件传递到 Pipeline 后, 它其实是以 tail 为起点开始传播的.而 tail.connect 其实调用的是 AbstractChannelHandlerContext.connect 方法:
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
ext.invokeConnect(remoteAddress, localAddress, promise);
//省略干扰代码
}
- findContextOutbound() 顾名思义, 它的作用是以当前 Context 为起点, 向 Pipeline 中的 Context 双向链表的前端寻找第一个 outbound 属性为真的 Context(即关联着 ChannelOutboundHandler 的 Context), 然后返回。找到了一个 outbound 的 Context 后, 就调用它的 invokeConnect 方法, 这个方法中会调用 Context 所关联着的 ChannelHandler 的 connect 方法:
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
connect(remoteAddress, localAddress, promise);
}
}
- 如果用户没有重写 ChannelHandler 的 connect 方法, 那么会调用 ChannelOutboundHandlerAdapter 所实现的方法:
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
ChannelOutboundHandlerAdapter.connect 仅仅调用了 ctx.connect,因此又回到了(2),继续寻找outboundhandler,直到 connect 事件传递到DefaultChannelPipeline 的双向链表的头节点, 即 head 中,在head节点中调用unsafe.connect方法:
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
unsafe.connect(remoteAddress, localAddress, promise);
}
下面以一幅图来描述一个整个 Connect 请求事件的处理过程:
5. 总结
Inbound 事件传播方法有:
ChannelHandlerContext.fireChannelRegistered()
ChannelHandlerContext.fireChannelActive()
ChannelHandlerContext.fireChannelRead(Object)
ChannelHandlerContext.fireChannelReadComplete()
ChannelHandlerContext.fireExceptionCaught(Throwable)
ChannelHandlerContext.fireUserEventTriggered(Object)
ChannelHandlerContext.fireChannelWritabilityChanged()
ChannelHandlerContext.fireChannelInactive()
ChannelHandlerContext.fireChannelUnregistered()
如果我们捕获了一个事件, 并且想让这个事件继续传递下去, 那么需要调用 Context 相应的传播方法.
和connect类似的Outbound事件还有:
ChannelHandlerContext.bind(SocketAddress, ChannelPromise)
ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise)
ChannelHandlerContext.write(Object, ChannelPromise)
ChannelHandlerContext.flush()
ChannelHandlerContext.read()
ChannelHandlerContext.disconnect(ChannelPromise)
ChannelHandlerContext.close(ChannelPromise)
注意handler和context的的区别,比如context中connect方法是事件传输的介质,handler中connect是真正的处理事件。
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
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
下篇介绍Netty中最最核心的EventLoop