###################################################################################
后续文章中都会对一些代码逻辑进行说明,但原文的英文注释一般不会直译,进行保留,只会说明那些没有注释的地方
###################################################################################
本文中关联的所有文章的总目录可以参看:系列文章目录
1前言
前面我们已经了解了NioEventLoopGroup这个类的创建流程以及其中的业务。如果没有了解的可以参看这个连接:Netty 源码分析一 NioEventLoopGroup创建逻辑
而看完上面的类创建后,我们需要了解更多的该类功能,而该类的功能我们不可能一个方法一个方法的看其被什么地方调用,这样太乱,我们还是按DiscardServer类中的代码逻辑来看
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new DiscardServerHandler());
}
});
上面这一块代码跟NioEventLoopGroup相关的也就是调用ServerBootstrap 类的group方法,这个方法其实只是进行了一些内部变量的赋值,没有什么特殊的额外的逻辑。那我们就再看bind方法
b.bind(PORT)
2 bind方法逻辑
2.1 方法的执行的序列图
从其中的序列图中可以看出,主要的逻辑还是在AbstractBootstrap这个父类的doBind(final SocketAddress localAddress)方法中来执行的;我们来具体看下这块的代码
2.1.1 doBind方法
private ChannelFuture doBind(final SocketAddress localAddress) {
// 这里就是根据b.channel(NioServerSocketChannel.class)设置的渠道类型实例化该类,
// 并且对这个类进行一些初始化设置,具体的大致流程可以参看上面的序列图中4~11这些步骤,
// 返回的实际上是DefaultChannelPromise这个对象.
final ChannelFuture regFuture = initAndRegister();
// 实际上调用的是DefaultChannelPromise 类中的channel,而该类中设置的channel是NioServerSocketChannel
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
// 如果前端register没有问题,此处就不会进来
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
1. doBind方法内部的initAndRegister 方法
上面方法代码第一条就是执行initAndRegister方法,而该方法的大致逻辑
就是根据b.channel(NioServerSocketChannel.class)设置的渠道类型实例化该NioServerSocketChannel类,并且对这个类进行一些初始化设置,具体的大致流程可以参看上面的序列图中12~15这些步骤,而详细的代码如下所示:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 实例化该NioServerSocketChannel类
channel = channelFactory.newChannel();
// 调用其子类ServerBootstrap中的一些ServerSocketChannel渠道的设置
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 1. config()是调用其父类的这个重写方法,而该方法实际上创建的是ServerBootstrapConfig(ServerBootstrap) 这个类;
// 2. ServerBootstrapConfig.group()方法实际上调用的就是ServerBootstrap类中赋值的parentGroup所对应的NioEventLoopGroup这个对象;
// 3. register(channel), 实际上执行的就是NioEventLoopGroup类的register方法,传入的是 NioServerSocketChannel这个对象
// 4. 最后返回的regFuture 是DefaultChannelPromise这个类的对象数据
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
至此我们可以看到再次使用NioEventLoopGroup 类的register(Channel) 方法的逻辑,这个里面具体执行了哪些业务,我们放到后续的文章中进行说明,在此先省略;我们继续看后续的业务执行。
1.1 initAndRegister 内部执行的 regFuture.cause()
而对于regFuture.cause()这句代码,实际上执行的是如下的代码
@Override
public Throwable cause() {
return cause0(result);
}
private Throwable cause0(Object result) {
// 如果前面在执行config().group().register(channel)这行代码注册成功后,
// result里面放入的只是一个Object对象,返回result类型不会是CauseHolder,因为方法返回的是null
if (!(result instanceof CauseHolder)) {
return null;
}
if (result == CANCELLATION_CAUSE_HOLDER) {
CancellationException ce = new LeanCancellationException();
if (RESULT_UPDATER.compareAndSet(this, CANCELLATION_CAUSE_HOLDER, new CauseHolder(ce))) {
return ce;
}
result = this.result;
}
return ((CauseHolder) result).cause;
}
1.2 initAndRegister 内部的init 方法执行
init方法我们通过序列图也可以看出来,它实际上执行的是ServerBootstrap 类中实现的init方法,具体的代码如下所示:
@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
// ======== 1 =====后续特殊说明标识=======
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
// 这个ch就是NioServerSocketChannel
// =========== 2 =====后续特殊说明标识=======
final ChannelPipeline pipeline = ch.pipeline();
// config是ServerBootstrapConfig这个类,而这个类其实很简单,就是针对ServerBootstrap设置的信息进行封装,在服务设置时的
// group,channel,option,handler,childHandler都可以通过该config进行访问;
// 所以config.handler()这个方法返回的就是ServerBootstrap.handler里面设置的 LoggingHandler这个配置(这个是针对EchoServer这个main方法启动来说的)
// ========= 3=====后续特殊说明标识=======
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));
}
});
}
});
}
- 上面代码中说明下,针对标识中2的initChannel方法传入的Channel参数进行特殊说明下:
并不是Channel参数传入的一定会是NioServerSocketChannel这个对象;需要根据上下文的代码来确定传入的具体是哪个Channel;
那上面为什么传入的会是NioServerSocketChannel这个对象了?
因为在往p.addLast() 也就是上面代码中标识为1的在进行添加时,是通过的DefaultChannelPipeline来添加的,而该类又是NioServerSocketChannel对象中的pipeline;
而ChannelInitializer类的initChannel这个接口方法原文的注释翻译后如下所示:
通道注册后将调用此方法。 方法返回后,该实例将从Channel的ChannelPipeline中删除。
所以在通道注册成功后就会通过通道中的ChannelPipeline来调用这个方法;而NioServerSocketChannel也是一个通道,它在initAndRegister这个方法中我们通过源码可以知道,它最后会被注册;而注册的这块具体逻辑,我们可以参看后面的文章可以提前了解相应的逻辑;Netty 源码分析一 NioEventLoopGroup类的register方法,而针对注册成功后会调用这些通过addLast添加到pipeline里面的ChannelHandler是在代码哪里被调用的,我们可以参看这篇文章的具体分析:
- 上面代码中针对标识为3的,我在这里特殊说明下,并不是所有的config.handler()返回的都是LoggingHandler这个对象,为什么我在这里说加入的是这个对象了?是因为我这个系统文章都是以EchoServer (代码可以参看 Netty 源码分析入口 这里)这个类里面的代码来说明的;Netty 源码分析一 NioServerSocketChannel类
2. doBind方法中doBind0方法
当initAndRegister 方法执行完后,我们通过前面的doBind方法的代码也可以看出在NioServerSocketChannel通道注册成功后,会执行doBind0这个方法,而这个方法中大致处理的内容就是当渠道注册成功后,就会进行地址的绑定,并且会添加一个关闭通道时的监听器
private static void doBind0(final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
// 实际上执行的是NioServerSocketChannel的bind方法
channel.bind(localAddress, promise)
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
通过上面的代码我们可以知道在往NioServerSocketChannel里面的NioEventLoop线程池加入了一个渠道注册的任务;
至此我们分析了ServerBootstrap的bind方法主要执行的逻辑,而在这个执行逻辑中还涉及到了以下几点的执行:
NioEventLoopGroup.register 方法的逻辑
NioServerSocketChannel.bind(SocketAddress localAddress, ChannelPromise promise) 方法的逻辑
所以下一篇我们还是回归到前一篇的NioEventLoopGroup这个类的源码学习;把这个类学习完后,我们再学习其它相关联的内容