###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################
本文中关联的所有文章的总目录可以参看:系列文章目录
文章目录
1. 前言
因为前面一篇我们在讲解NioEventLoop 父类中 SingleThreadEventLoop.register(final ChannelPromise promise) 方法时最终会调用NioServerSocketChannel 这个类的unsafe方法创建出来的对象中的register(EventLoop eventLoop, final ChannelPromise promise) 方法;
所以我们需要了解这个类的情况,然后才能知道上篇文章的最后处在 SingleThreadEventLoop类的下面代码究竟调用的是什么?处理了哪些逻辑?
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
// promise.channel() 实际上是NioServerSocketChannel这个对象
// NioServerSocketChannel 这个对象的unsafe方法创建出来的对象中的register
promise.channel().unsafe().register(this, promise);
return promise;
}
下面我们就要学习这块代码的内容
2. 类的继承关系
在学习之前,我们首先要了解到这个类的继承关系,这样才能知道个大概映像;
通过这个类的继承关系,我们知道它是一个Channel的子类,而该接口具体有什么特性?我们在Netty源码分析一接口系列Channel接口 这篇文章中有介绍,在这里就不再重复了;
3. 类的创建
这里说的类的创建是在ServerBootstrap类中创建该类的流程。而该类创建的时机是在Netty 源码分析一ServerBootstrap的bind方法这篇文章中讲解过;在讲解1. doBind方法内部的initAndRegister 方法这部分时触发了NioServerSocketChannel 类的实例化;具体实例化通过这篇文章讲解的ServerBootstrap类的bind方法序列图时也知道,是通过ReflectiveChannelFactory这个类的newChannel来构建的;
3.1 类的创建的序列图
3.2 类创建后一些属性赋值情况
该类的创建逻辑相对而言比较简单,主要是对下面一些属性进行了赋值:
类名 | 属性类型 | 属性名 | 赋值 |
---|---|---|---|
NioServerSocketChannel | SelectorProvider | DEFAULT_SELECTOR_PROVIDER | SelectorProvider.provider(),windows系统默认是的WindowsSelectorProvider这个对象 |
NioServerSocketChannel | ServerSocketChannelConfig | config | NioServerSocketChannelConfig |
AbstractNioChannel | SelectableChannel | ch | 通过这种方式SelectorProvider.provider().openServerSocketChannel()创建出来的ServerSocketChannelImpl对象 |
AbstractNioChannel | int | readInterestOp | SelectionKey.OP_READ |
AbstractChannel | Channel | parent | null |
AbstractChannel | ChannelId | id | DefaultChannelId.newInstance() |
AbstractChannel | Unsafe | unsafe | new NioMessageUnsafe() 调用的是这个类的父类AbstractNioMessageChannel的newUnsafe方法创建的 |
AbstractChannel | DefaultChannelPipeline | pipeline | new DefaultChannelPipeline(this) |
4. register 方法介绍
在这一篇文章中Netty 源码分析一 NioEventLoopGroup类的register方法,我们已经知道最后register方法会调用这个类中封装的unsafe对象中的register方法;
而NioServerSocketChannel 这个类的unsafe对象,我们通过该对象的创建时就知道,unsafe是NioMessageUnsafe这个类实例;而NioMessageUnsafe这个类是写在AbstractNioMessageChannel类中的内部类;
那我们要对这个类也要有一些大致的概念,看下它的类的继承关系:
在Netty源码分析一接口系列Channel接口这篇文章中我们讲解过Unsafe接口具体有哪些作用。
// eventLoop 参数就是为了保证这个NioEventLoop是否在当前线程中执行?如果不在就放到线程中去执行,如果已经在就直接注册。
// promise 参数就是为了调用方最终能够通过 ChannelFuture 接口中知道注册的结果如何?因为ChannelPromise 类继承了ChannelFuture接口
register(EventLoop eventLoop, final ChannelPromise promise);
上面的接口最终会执行下面的方法:
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = this.neverRegistered;
// 而这个方法就是在AbstractNioChannel抽象类中进行实现的
doRegister();
this.neverRegistered = false;
AbstractChannel.this.registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
AbstractChannel.this.pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
if (firstRegistration) {
AbstractChannel.this.pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
AbstractChannel.this.closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
而在AbstractNioChannel类中的doRegister() 方法具体的代码如下所示:
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 1. javaChannel()这个方法返回的就是在NioServerSocketChannel创建时的ch这个属性值,
// 也就是jdk原生的SelectorProvider.provider().openServerSocketChannel() 创建出来的对象
// 2.最终调用的就是ServerSocketChannelImpl.(Selector sel, int ops, Object att)方法
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
到目前为止,我们通过Netty 源码分析一 NioEventLoopGroup类的register方法 这篇和本篇文章将register方法做的主要逻辑理顺了;
4.1 调用通道添加事件pipeline.invokeHandlerAddedIfNeeded()方法
通过前面的代码中我们可以了解这个方法调用前原文的注释是这样的:
确保在我们实际通知承诺之前调用handlerAdded(…)。 这是必需的,因为用户可能已经通过ChannelFutureListener中的管道触发了事件。
这行代码的功能就是让需要添加的通道添加到pipeline中,而这个代码调用链路我们可以先看下:
在第1步就是pipeline.invokeHandlerAddedIfNeeded() 这个方法执行,然后在步骤2中我们可以看下逻辑:
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
assert !registered;
// This Channel itself was registered.
registered = true;
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
// Null out so it can be GC'ed.
this.pendingHandlerCallbackHead = null;
}
// This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
// holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
// the EventLoop.
PendingHandlerCallback task = pendingHandlerCallbackHead;
while (task != null) {
task.execute();
task = task.next;
}
}
上而代码其实最终就是执行 this.pendingHandlerCallbackHead 这个对象的execute()方法,执行完后再去调用这个对象的next元素,只要有就再调用这个元素的execute()方法,如此轮循,直到next元素为空;
而pendingHandlerCallbackHead 这对象在什么时候进行赋值的值,我们需要先了解后,才能知道它为什么会调用ChannelInitializer接口中的initChannel方法;
这个其实就是在我们往pipeline添加ChannelHandler时会进行赋值,只要通道没有注册(即pipeline通道中的registered变量不为true,而这个值是在第二步callHandlerAddedForAllHandlers()方法执行时才会被置为true);
我们拿addLast方法为例,不管调用DefaultPipeline哪个addLast方法,都会调到下面这个方法中:
@Override
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 the registered is false it means that the channel was not registered on an eventLoop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
// =================
// registered 不为true时,就会调用下面这个方法
// =================
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
callHandlerCallbackLater 代码如下所示:
private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
assert !registered;
PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
PendingHandlerCallback pending = pendingHandlerCallbackHead;
if (pending == null) {
pendingHandlerCallbackHead = task;
} else {
// Find the tail of the linked-list.
while (pending.next != null) {
pending = pending.next;
}
pending.next = task;
}
}
所以我们再通过上面的代码就知道了pendingHandlerCallbackHead 这个对象就是 new PendingHandlerAddedTask(ctx) 来创建出来的;而该PendingHandlerAddedTask对象中的next值是空,没有进行赋值;
而在没有注册前就调用pipeline通道的addLast,addFister等等方法,只有ServerBootstrap类中的init(Channel channel)方法中有添加过;代码如下所示:
@Override
void init(Channel channel) {
// 传入的channel就是在创建服务时设置的NioServerSocketChannel 这个channel对象;该对象是在父类的initAndRegister中进行实例化的;
setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));
// 而通过NioServerSocketChannel这个类在初始化时构建的对象中知道pipeline这个属性实际指向的是 DefaultChannelPipeline 这个对象
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = this.childGroup;
final ChannelHandler currentChildHandler = this.childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = this.childOptions.entrySet()
.toArray(newOptionArray(0));
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = this.childAttrs.entrySet()
.toArray(newAttrArray(0));
// 这个是在DefaultChannelPipeline 这个通道中加入新的ChannelHander
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
// 这个ch就是NioServerSocketChannel
final ChannelPipeline pipeline = ch.pipeline();
// config是ServerBootstrapConfig这个类,而这个类其实很简单,就是针对ServerBootstrap设置的信息进行封装,在服务设置时的
// group,channel,option,handler,childHandler都可以通过该config进行访问;
// 所以config.handler()这个方法返回的就是ServerBootstrap.handler里面设置的 LoggingHandler这个配置(这个是针对EchoServer这个main方法启动来说的)
ChannelHandler handler = ServerBootstrap.this.config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup,
currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
上面的一些说明在 Netty 源码分析一ServerBootstrap的bind方法这篇文章中init(Channel channel) 这个方法说明时有更加详细的说明,在这里就不再重复了;
通过上面的代码,我们可以知道在通道注册前pipeline中加了一个ChannelHandler是new ChannelInitializer(){…},而在通道注册后,会调用ChannelInitializer类中的initChannel接口,然后再往内部添加了两个ChannelHandler对象;
其中一个是在ServerBootstrap类创建时的LoggingHandler对象;
另一个是异步添加的ServerBootstrapAcceptor对象
而通过上面的代码ch.eventLoop().execute() 方法调用我们也可以了解到了,NioServerSocketChannel类中的NioEventLoop这个线程池中添加一个task;task主要的任务就是在pipeline通道中加入ServerBootstrapAcceptor
而ChannelInitializer 中的initChannel()方法被调用链路我们可以看到如下所示:
所以只要是往NioServerSocketChannel这个对象中的pipeline这个里面添加了ChannelHandler 对象,在invokeHandlerAddedIfNeeded()方法调用后,一旦调用成功后,会从pipeline这个通道中移出掉;这个我们通过ChannelInitializer 这个抽象类的 initChannel抽象方法原文注释中可以了解到:
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
protected abstract void initChannel(C ch) throws Exception;
而通过前面的调用链路,标红的后面有数字4的ChannelInitializer抽象类的handlerAdded(ChannelHandlerContext ctx)方法中也可以看到具体的代码;
/**
* {@inheritDoc} If override this method ensure you call super!
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
// This should always be true with our current DefaultChannelPipeline implementation.
// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
// will be added in the expected order.
if (initChannel(ctx)) {
// We are done with init the Channel, removing the initializer now.
removeState(ctx);
}
}
}
通过前面的分析我们可以总结如下:
- pipeline.invokeHandlerAddedIfNeeded() 方法执行后,一般是会调用ServerBootstrap类在init方法中加入到pipeline中的ChannelInitializer接口的initChannel方法;
- 而initChannel方法中以会往pipeline通道中加入服务配置的ChannelHandler(即LoggingHandler对象);
- NioServerSocketChannel类中的NioEventLoop这个线程中再往pipeline通道中加入一个ServerBootstrapAcceptor;
- 在调用完ChannelInitializer接口的initChannel方法后,将这个ChannelHandler移出;
4.2 调用通道注册成功事件 pipeline.fireChannelRegistered() 方法
在前面的讲解中register0方法里面还是有通道方法调用的,比如这个方法调用AbstractChannel.this.pipeline.fireChannelRegistered(); 这个方法调用就是在通道注册成功后,调用还在通道中的ChannelHandler这些元素的channelRegistered方法;
4.3 通道激活事件pipeline.fireChannelActive()调用
这个方法要想被执行,是有条件的,我们通过前面讲的register0 这个方法的代码可以知道:pipeline.fireChannelActive()方法,先通过isActive()能够通过,并且必须是firstRegistration时才会被调用,而该块代码的原文注释说明如下所示:
仅是在通道重未注册时才会调用ChannelActive,这是为了防止已经销毁并重新注册的这种情况,这样就不会存在多个通道处于活动的现象;