上篇中,整体分析了doBind
方法,但是遗留了 register0
和 doBind0
方法。
register0执行
核心代码在 AbstractChannel
的 register
入口:
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
上面方法首先会判断是否为eventLoop
中执行,即是否当前方法由reactor
线程执行。
在 SingleThreadEventExecutor
中,判断依据为:
@Override
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
否则会将其推入任务队列中执行。
register0详解
private void register0(ChannelPromise promise) {
try {
// 判断是否能注册
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 实际注册方法,即调用nio底层channel进行注册
doRegister();
neverRegistered = false; // 设置变量
registered = true;
// 执行handlerAdded方法
pipeline.invokeHandlerAddedIfNeeded();
// 告知其他监听着
safeSetSuccess(promise);
pipeline.fireChannelRegistered(); // 调用pipeline的channelregister方法
// 准确来说,只有首次被注册,才能调用active方法。这里判断原因是因为一个channel可能被deregistered 再 re-registered.
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// 如果先前就被注册过滤,并且被设置为autoRead,那么就要开始处理读事件
beginRead();
}
}
} catch (Throwable t) {
// 报错处理
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
整个register0
代码逻辑在上面都有了注释,主要包括:
- 调用java nio底层进行selector与channel注册。
- 调用
pipeline.invokeHandlerAddedIfNeeded()
执行channelAdd
方法。 - 调用
pipeline.fireChannelRegistered()
执行channelRegistered
方法。 - 调用
pipeline.fireChannelActive()
执行channelActive
方法。
pipeline.invokeHandlerAddedIfNeeded()
pipeline.invokeHandlerAddedIfNeeded()
目前在netty中,主要是链式调用 handlerAdded
方法。
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
assert !registered;
registered = true;
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
// 每次调用完,会被清除
this.pendingHandlerCallbackHead = null;
}
// 链式执行,并且放在synchronized外面,防止一直持有锁
PendingHandlerCallback task = pendingHandlerCallbackHead;
while (task != null) {
task.execute();
task = task.next;
}
}
加入时机则是在每个ChannelHandler
的增加或者删除时机:
DefaultChannelPipeline
的 callHandlerCallbackLater
方法:
这样当调用添加或者删除时,则会通过 ChannelHandlerContext
进行链式调用 handlerAdded
或者 handlerRemoved
方法。
pipeline.fireChannelRegistered() 和 pipeline.fireChannelActive()
这两个方法调用逻辑其实是类似的,通过Pipeline进行链式handler调用,以 pipeline.fireChannelRegistered
为例:
DefaultChannelPipeline
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
此处的 head
则是表示 DefaultChannelPipeline
的头结点。
AbstractChannelHandlerContext
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();
}
});
}
}
再到 invokeChannelRegistered
方法:
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
从上面 invokeChannelRegistered
可知:
- 通过
invokeHandler
方法, 只会对已加入,并且执行完handlerAdded
方法的channelHandler
进行操作。 - 会捕获所有
Throwable
的异常,并进行通知。 - 然后是否往后面传递,取决于代码中是否显示调用
fireChannelRegistered
方法。
doBind0
从doBind方法中可以了解,只有注册成功后,才能执行doBind0方法,否则加一个register的监听器进行操作:
从 AbstractBootstrap
的 doBind0
开始:
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
注册成功后,执行 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
AbstractChannel
的 bind
方法:
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
从上面代码可以猜出大概,就是通过DefaultChannelPipeline
将绑定时间传递出去。
DefaultChannelPipeline
的bind
方法:
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
执行tail
的bind
方法。
AbstractChannelHandlerContext
的bind
方法:
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (isNotValidPromise(promise, false)) {
return promise;
}
// 找到一个有bind事件的ChannelHandlerContext
final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
最终找到了 DefaultChannelPipeline$HeadContext
的 invokeBind
方法,实际上是父类 AbstractChannelHandlerContext
的方法:
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
同样类似的写法,最终进入到 HeadContext
的bind
方法:
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
unsafe.bind(localAddress, promise);
}
由于是server端,所以此处unsafe
为io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe
NioMessageUnsafe 的 bind方法
NioMessageUnsafe
的 bind
方法 整体保持和register一样的写法,前面一些判断后,直接进入了doBind
方法中:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
。。。
boolean wasActive = isActive();
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
最终仍然是调用nio,将channel与地址绑定起来:
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
总结
本文主要细致了解了netty在doBind方法中两个细节:
- channel和selector注册逻辑,以及事件传播逻辑。
- channel在注册成功后,与ip地址绑定逻辑。
关注博主公众号: 六点A君。
哈哈哈,一起研究Netty: