在上一篇 Netty学习笔记:1.从入门示例分析ChannelHandler的使用 中,我们使用了非常简单的入门示例,那么Netty是如何启动的呢?为什么启动后它就可以接收连接,接收数据了?数据是怎么一层层传递到各个Handler的?两个线程组是怎么工作的?带着这些问题,我们使用调试模式,一步步地跟踪Netty的整个启动过程,去寻找问题的答案。
目录
0.示例代码概述
我先把入门示例代码再贴一遍,然后逐步地分析
public class Server {
public void start(int port) {
new Thread("netty-server-thread") {
@Override
public void run() {
super.run();
NioEventLoopGroup bossGroup = null;
NioEventLoopGroup workerGroup = null;
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// ch.pipeline().addLast(new StringDecoder());
// ch.pipeline().addLast(new ServerHandler());
ch.pipeline().addLast(new WriteHandler1());
ch.pipeline().addLast(new WriteHandler2());
ch.pipeline().addLast(new ReadHandler1());
ch.pipeline().addLast(new ReadHandler2());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("[" + Thread.currentThread().getName() + "]" + Server.class.getSimpleName() + ": server start");
bootstrap.bind(port).sync().channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
}
}
}.start();
}
}
Netty服务就是靠Bootstrap启动类启动的,先给它设置了两个线程组,bossGroup用于监听客户端的连接、读写事件,workerGroup用于处理连接、读写事件。然后设置了此次使用的Channel类型是NioServerSocketChannel,即非阻塞的服务端Channel类型,感兴趣的读者请自行百度BIO和NIO的区别。接着设置了childHandler,就是处理连接、读写事件的处理类,他们会在workerGroup线程组的线程池里运行。option和childOption就是一些参数设定,这里不展开了。最后的一步,也是最关键的一步,前面的步骤都是一些初始化设置的东西,到这一步才是真正的启动服务,如绑定端口,启动线程组,监听事件等。closeFuture().sync()是阻塞线程,等待服务关闭,因为所有的客户端连接、读写事件都在bossGroup和workerGroup里执行,所以这个线程阻塞并不影响业务的运行。也正是因为这个线程会在最后阻塞,所以Server类在start的开始,就创建了一个新的线程new Thread()来执行netty服务的启动。finally里的代码,只有等到服务关闭,即等到closeFuture().sync()阻塞完后才会执行,因为服务关闭了,所以要关闭这两个线程组,以免造成资源浪费或者可能导致内存泄漏。
示例代码讲解完后,接下来我们从源码角度逐步分析示例代码的各个环节。
1.创建ServerBootstrap
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
private final Map<ChannelOption<?>, Object> childOptions = new ConcurrentHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
private volatile EventLoopGroup childGroup;
private volatile ChannelHandler childHandler;
public ServerBootstrap() { }
//其他代码...
}
logger就是一个日志框架,后续netty的信息输出都会使用到这个logger打印日志,日志框架不在本章讨论范围,感兴趣的读者可以百度之,比如slf4j,log4j,logback等主流日志框架。
然后是初始化用于保存选项的childOptions和保存属性的childAttrs,因为ServerBootstrap继承于AbstractBootstrap,所以也会初始化AbstractBootstrap类的一些属性,如下面代码段中的options和attrs
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
volatile EventLoopGroup group;
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory;
private volatile SocketAddress localAddress;
private final Map<ChannelOption<?>, Object> options = new ConcurrentHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
private volatile ChannelHandler handler;
AbstractBootstrap() {
// Disallow extending from a different package.
}
//其他代码...
}
然后创建一个ServerBootstrapConfig的配置类
public final class ServerBootstrapConfig extends AbstractBootstrapConfig<ServerBootstrap, ServerChannel> {
ServerBootstrapConfig(ServerBootstrap bootstrap) {
super(bootstrap);
}
public EventLoopGroup childGroup() {
return bootstrap.childGroup();
}
public ChannelHandler childHandler() {
return bootstrap.childHandler();
}
public Map<ChannelOption<?>, Object> childOptions() {
return bootstrap.childOptions();
}
public Map<AttributeKey<?>, Object> childAttrs() {
return bootstrap.childAttrs();
}
}
这个类的构造器是将ServerBootstrap对象传进去,里面提供了一些获取options, attrs, childOptions, childAttrs等属性的方法,这个其实我挺疑惑的,这个config在ServerBootstrap的声明是private的,说明不是提供给外界使用的,然而options, attrs, childHandler等这些属性,ServerBootstrap本身就可以获取到,这个config感觉有点多此一举了。
2.创建线程组bossGroup和workerGroup
这两个对象,其实都是NioEventLoopGroup,所以这里只讲一遍bossGroup的创建过程
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(MultithreadEventLoopGroup.class);
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
if (logger.isDebugEnabled()) {
logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
}
}
//其他代码...
}
NioEventLoopGroup继承于MultithreadEventLoopGroup,所以第一步执行MultithreadEventLoopGroup里的静态属性和静态代码块,首先也是初始化一个logger,然后静态代码块里,初始化了一个DEFAULT_EVENT_LOOP_THREADS的整数,即默认线程数量,这里它的取值是有效核心处理器的数量乘以2,我本地调试的结果是12,那说明我的电脑CPU是六核的,于是去中关村搜了一下CPU的型号,确实是六核的(来自程序猿的严谨态度,哈哈)
初始化完父类MultithreadEventLoopGroup的静态代码后,回到了NioEventLoopGroup本身的构造器,它有多个构造器,逐个逐个地调用,如下所示
public class NioEventLoopGroup extends MultithreadEventLoopGroup {
public NioEventLoopGroup() {
this(0);
}
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}
//其他代码...
}
一直调用到最后一个构造器,就会调用到父类的构造器,如下所示
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
//...
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
//...
}
因为我们示例代码是使用了不带参数的构造器,所以使用的线程数量为0,到了父类构造器这里,如果是0,就会采用之前初始化的默认线程数量,之前我们调试得到的数量是12。在实际的项目应用中,我们可以根据系统的业务大小调整线程数量,比如你为公司做了一个简单的考勤系统,公司只有二三十个人,而且考勤系统基本上只有上下班的时间才会使用,那么bossGroup和workerGroup各使用12个线程真的非常浪费资源,对于这种用户少,并发量低的系统,完全可以使用一两个线程就可以应付了。你可以像如下设置创建两个线程组
public class Server {
public void start(int port) {
//...
ServerBootstrap bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup(2);
//...
}
}
MultithreadEventLoopGroup的构造器继续调用它的父类MultithreadEventExecutorGroup的构造器,传参executor为空,所以这里会为它创建类型为ThreadPerTaskExecutor的执行器,顾名思义就是一个任务对应一个线程的执行器,newDefaultThreadFactory内部会为以后创建的线程名称设定前缀为nioEventLoopGroup-2-,比如我本地默认线程数量是12,所以之后如果启用线程组并创建线程,那么里面的线程名称就是nioEventLoopGroup-2-1,nioEventLoopGroup-2-2,,,nioEventLoopGroup-2-12。
接下来的代码使用for循环,依次创建EventExecutor,可以理解为这就是线程组里面的12个线程,如果创建过程中,发生了异常,则会关闭之前创建好的线程(其实不是很理解netty官方的用意,后面的线程创建失败,跟前面的线程有什么联系吗?)
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
//...
}
上面的构造器,初始化完12个线程后,下面还有一段逻辑,如下所示
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
//...
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
//...
}
chooser是轮到线程池执行线程时,选择哪个线程执行的选择器,它的内部实现有两个选择器,如果线程数量是2的N次方,则使用PowerOfTwoEventExecutorChooser,如果不是,则使用GenericEventExecutorChooser,即通用的选择器,因为我的线程数量是12,不是2的N次方,所以使用了后者。(至于这两个选择器的机制是怎么样的,我在这里就不深入研究了,因为对于一般的简单业务系统而言,只要你保证线程数量够用,业务不会受线程数量,它内部怎么选择线程的,我们可以不用关心)
//...
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
//...
继续看构造器下面的代码,它给每一个线程添加了监听器,监听线程terminate(终结,终止,结束?)的时候采取的措施。terminatedChildren是一个AtomicInteger,我对这段代码的字面理解是,每当有一个线程terminate的时候,这个terminatedChildren就自增1,当它达到和线程组数量一致时,就设置terminateFuture的success为空。。。好吧,我不知道这个后面哪里会用到,因为目前程序跑到这里,线程组只是初始化,还没有真正开始使用。
构造器的最后三行,是把这12个线程组又放进了一个Set集合里面,这个Set集合应该是方便以后使用遍历器Iterator做一些操作而增加的,比如遍历每个线程,然后让它终止terminate。至此,bossGroup的创建就完成了,workerGroup也是一样的流程,就不再赘述了。
3.设置线程组bossGroup和workerGroup
将创建好的两个线程组赋值给ServerBootstrap,其中bossGroup赋值给父类AbstractBootstrap的group,workerGroup赋值给ServerBootstrap的childGroup,都是比较简单的操作,就不贴代码了
4.设置通道类型
执行到示例代码的.channel(NioServerSocketChannel.class)这一行,我们继续跟进,可以看到它是创建了一个通道工厂,后面系统需要Channel的时候,就使用这个channelFactory工厂生产出一个NioServerSocketChannel
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
至于这个工厂是如何生产的,我们可以进到ReflectiveChannelFactory里面,其实在进去之前,就可以根据Reflective这个单词知道,内部肯定是通过Java的反射机制实现的工厂,我们进去看看代码。
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz)
+ " does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " +
constructor.getDeclaringClass(), t);
}
}
}
果然不出所料,工厂把NioServerSocketChannel的构造器保存了下来,生产时调用newChannel方法,该方法直接调用保存下来的构造器新建一个实例,然后返回给消费者。
5.设置Handler
//...
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws
Exception {
// ch.pipeline().addLast(new StringDecoder());
// ch.pipeline().addLast(new ServerHandler());
ch.pipeline().addLast(new WriteHandler1());
ch.pipeline().addLast(new WriteHandler2());
ch.pipeline().addLast(new ReadHandler1());
ch.pipeline().addLast(new ReadHandler2());
}
})
//...
回到开头的示例代码,childHandler方法传入的ChannelInitializer,它的父类是ChannelInboudHandlerAdapter,至于下方的initChannel方法,调试时没有执行,我猜测是后面Channel被激活后(比如客户端创建连接后)才会调用执行Channel的初始化。
6.设置选项option和childOption
SO_BACKLOG这个参数,从字面上很难理解它的用意,于是百度了一下,它是创建连接时的最大等待数量,比如某个系统高并发的情况很频繁,某一个时刻同时有100个连接请求发送过来,而CPU一次只能处理一个连接(不知道我的六核CPU是不是可以同时处理6个连接请求呢?)那么,这100个请求,当前时刻只有1个在处理,其他99个都在排队等待着。那如果是200个连接请求同时到来,那么只有前128个请求会进入等待队列里,后面来的只能丢弃了。
注意:有的读者可能理解成,设置了这个SO_BACKLOG=128后,我的系统只能允许创建128个连接了,不是这样的,我们前面提到的是“同时”。我举一个比较形象的例子,某一家银行的某个营业厅(服务器),它里面有一个柜台小姐姐(CPU),营业厅9点开门后,一下子来了200个人要来取钱或者存钱(请求连接),但是营业厅太小了,只能容纳128个人排队等候,于是保安就在门口拦住了后面想要进来的人,但是只要柜台里处理完了一个业务,下一个人就会去柜台办理,等候的128个位置就会空出一个,如果这时外面有人问可以进去了吗(请求连接),保安看到有空位就会让他进去等候。所以,只要营业厅还没下班(服务器没有停止服务),小姐姐就会一直办理业务,开门时同时进来的128个人办理完后,后面陆续来的人都可以办理业务,如果小姐姐业务能力强,速度快(CPU性能好),一天可以办理一万个业务都有可能。
//...
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
//...
SO_KEEPALIVE,即字面理解的意思,保持存活状态,设置为true后,服务器会定时检测TCP连接是否正常,百度了一下说默认是2个小时,如果客户端有回应,则在下一个2小时后继续检测,当然是指该连接没有任何数据交互的前提,只要有数据交互,服务器的定时器就会清零,重新定时2小时(我个人觉得这个KEEPALIVE就很鸡肋了,2个小时才检测一次,如果服务器本来就资源紧缺,然后有大量的连接获取了数据后不主动断开,造成CPU和内存的满负荷,后面的连接都无法进来了。还是拿上面银行的例子举例,大家办理了业务后,都不离开营业厅,全部挤在里面,后面的人都没法进来办理业务,这对于银行和没有办理到业务的人都是一种损失。所以我在实际的项目应用中都是在业务层自己定义心跳包,比如5秒或者10秒一次,超过两三个心跳包周期都没有收到心跳包,则认为客户端离线了,主动把这个TCP连接断开、清除,回收系统资源)
7.绑定端口,启动服务
回到示例代码,bootstrap.bind(5000),5000是本地绑定的端口,我们跳过一小部分无关紧要的代码,来到AbstractBootstrap的doBind方法里面
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
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;
}
}
上面代码的主要逻辑是,首先初始化和注册一个Channel,前面设置过通道类型为NioServerSocketChannel,所以这里肯定也是会初始化成NioServerSocketChannel类型,注册的意思是,将这个Channel注册到监听线程组,告诉线程组,我需要你帮我监听连接和读写事件,当事件来了,你就告诉我(调用相关的回调方法,最后会调用到ChannelHandler的active, inactive, read, readComplete等回调方法里面)
下一步判断初始化和注册是否已完成(初始化和注册是异步的),如果完成了,执行绑定端口操作,如果没完成,则给初始化注册的操作添加监听事件,监听它什么时候完成了再执行绑定端口的操作。
我们再深入到初始化和注册的方法initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
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);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
//...
return regFuture;
}
方法里面通过channelFactory工厂生产了一个新的NioServerSocketChannel,然后执行init(channel),如果初始化报错了,则关掉这个Channel,然后返回这个报错结果。如果没有报错,则会执行register注册,可以看到是group()调用的注册,所以就是将该Channel注册到group线程组里(应该是bossGroup,后面调试跟进一下)如果注册也失败了,同样也要关掉这个Channel
我们继续深入init(channel)方法里面
void init(Channel channel) {
setChannelOptions(channel, options0().entrySet().toArray(newOptionArray(0)), logger);
setAttributes(channel, attrs0().entrySet().toArray(newAttrArray(0)));
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
childOptions.entrySet().toArray(newOptionArray(0));
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = 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));
}
});
}
});
}
前两行就是把示例代码设置的option和attrs(示例代码没有设置attrs)赋值给该Channel,然后获取该Channel的管道pipeline,给这个管道添加了一个ChannelInitializer,本质是一个ChannelInboundHandlerAdapter,这个ChannelInitializer里面的initChannel也还没有执行,肯定也是后面线程组会执行到的。
init(channel)执行完后,又回到initAndRegister方法,继续执行下面的register注册操作
final ChannelFuture initAndRegister() {
//...initChannel部分
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
调用config().group()获取到了parentGroup,也就是我们示例代码传进来的bossGroup,然后在这个线程组进行Channel的register注册,进到register方法
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
这里的next是取线程组里的下一个线程来处理register操作,怎么取下一个线程,这个在前面初始化bossGroup时提到有一个线程池选择器,我们这里不关心它是怎么取下一个线程的,我们只关心它取到线程后,是如何进行注册的。继续跟进,我跳过了一些对于主要流程影响不大的代码(解读起来比较花时间,而且跳过也不影响Channel注册的代码),然后来到了AbstractChannel的register方法,如下所示
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
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是不是NioEventLoop(这个应该是初始化时设定好的,前面跳过了比较多的细节代码)。判断都没有问题后,执行到if(eventLoop.inEventLoop()),判断传进来的线程是否与当前对象保存的线程一致,因为bossGroup在这之前一直都没有启动过线程,所以这里对象保存的线程是null,不一致,所以执行了else的逻辑,else里面是调用了execute方法,即该线程启动执行,然后执行register0(promise)方法,我们在该方法里面打断点,然后运行程序,可以看到进入该方法后,线程切换到nioEventLoopGroup-2-1了,之前是netty-server-thread,即示例代码Server启动的线程。
进入到register0方法
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 = neverRegistered;
doRegister();
neverRegistered = false;
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.
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
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) {
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();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
判断还没注册过之后,继续进入到doRegister方法
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
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;
}
}
}
}
进入这个方法,可以看到是一个死循环,当然里面其实是有return的,就是注册完成后,或者异常抛错退出循环,这里的javaChannel(),实际就是返回Java原生的NIO包下的Channel了(补充知识:netty的NIO其实就是基于Java原生的NIO改造的),继续执行下一步发现直接到return了,是java包里面的方法没法进入吗?总之呢,执行了这一步,就是注册成功了。
我们继续回到register0方法,执行下面的步骤,pipeline.invokeHandlerAddedIfNeeded()
final void invokeHandlerAddedIfNeeded() {
assert channel.eventLoop().inEventLoop();
if (firstRegistration) {
firstRegistration = false;
// We are now registered to the EventLoop. It's time to call the callbacks for the ChannelHandlers,
// that were added before the registration was done.
callHandlerAddedForAllHandlers();
}
}
因为是第一次注册,所以会进入到判断里面,然后执行callHandlerAddedForAllHandlers()方法
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;
}
}
执行到while循环内部时,task.execute执行发现到了init(channel)时,往channel的管道pipeline增加ChannelHandler的initChannel方法,验证了之前我猜测它会在bossGroup线程启动时执行是对的
这里获取ServerBootstrap的handler,发现是空的,继续执行下面的操作
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = 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));
}
});
}
});
channel调用eventLoop执行了一个往管道增加一个ServerBootstrapAcceptor的操作,这个ServerBootstrapAcceptor我们暂且先不看,我们把callHandlerAddedForAllHandlers方法的while循环执行完,后面再重新断点进来看看里面做了什么操作。while的下一次循环,发现task为null,没有task需要执行了,然后又回到了register0方法,执行到pipeline.fireChannelRegistered,对Netty有一定了解的读者应该都知道,fireXXX方法,一般都是在管道中用来调用下一个ChannelHandler的回调方法的,我们继续执行
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
这里看到是调用了head的注册方法,head和tail是两个ChannelHandler,在初始化Channel的Pipeline时默认生成的,进入到invokeChannelRegistered(head)后
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();
}
});
}
}
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
这里的channelRegistered()方法就很熟悉了,就是ChannelHandler里面的回调方法
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
invokeHandlerAddedIfNeeded();
ctx.fireChannelRegistered();
}
这个方法内部又会继续的fireChannelRegistered,调用下一个ChannelHandler的回调方法,因为head的下一个是tail,所以又调用了tail的回调,我们执行tail的回调,发现tail的回调方法没有做任何操作
执行完后,我们又再次回到了register0方法,下一步判断是否active,判断是false,因为还没有客户端连接过来,所以这里register0方法就退出了。退出后,该线程任务就跑完了
继续执行跟进后,发现该线程还有任务队列没有完成,就是前面init(channel)方法时,往管道增加ServerBootstrapAcceptor的任务,在此时执行了,一顿跟踪后,终于发现了管道之所以叫管道的精髓,因为管道里面的ChannelHandler都是互相连接的,他们之间互相引用,也就是链表式的结构,而且添加Handler都是添加到tail之前(tail,尾巴的意思)
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
这个ServerBootstrapAcceptor添加到管道后,后面没有什么实际的操作了,我们把该次任务跑完,然后发现任务队列里还有任务,再跟进,我们发现这次的任务是doBind0里的一个线程任务,就是在执行initAndRegister之后,调用了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()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
跟进channel.bind方法,后面发现是使用了tail这个ChannelHandlerContext进行绑定的
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
在它的方法内部,又是找到了标识过绑定事件(MASK_BIND)的OutboundHandler来进行bind
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
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;
}
调试发现,这个带有MASK_BIND的HandlerContext,原来是head(HeadContext),跟进发现最终的bind操作,也是调用了Java原生的Channel进行bind
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
bind之后,线程又执行完了,再次找到了下一个任务就是fireChannelActive,很明显又是通知管道中的各个ChannelHandler,回调channelActive方法,从head开始,一直传递到tail,在head的channelActive回调方法中,使能了监听读的事件,readIfIsAutoRead(),因为之前有设置过autoRead=true,一时没想起来在哪一步了。。
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
readIfIsAutoRead();
}
active事件执行完之后,线程任务队列里就没有任务了,该线程退出,程序回到了bossGroup的 for主循环里,在这里不停地监听Selector是否有触发连接或者读或者写事件,如果有,就会调用不同的fireXXX方法,触发对这个事件感兴趣的ChannelHandler的回调方法。
到这一步就算是netty服务启动完成了。
8.优雅地关闭netty服务
前面netty服务启动完了,示例代码的线程在bind后阻塞了,那要如何正常地退出netty服务呢?我们只要对示例代码的bind那一行稍作改动,如下所示
ChannelFuture channelFuture = bootstrap.bind(port).sync();
//将绑定的通道Channel保存下来,想要停止服务时,调用该通道的close,下一行阻塞
//的方法就会往下执行到finally
serverChannel = channelFuture.channel();
serverChannel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
}
bind之后,阻塞直到绑定完成,返回channelFuture,然后从channelFuture中得到channel,这是一个ServerSocketChannel,即服务端的通道,我们只要在退出的时候调用serverChannel.close(),netty框架便会有计划地释放所有的Channel通道,包括用于客户端连接的通道,以及通道里的ChannelHandler
netty内部是如何具体实现正常关闭服务的,这里就不细说了,感兴趣的读者可以自己调试跟踪源码的实现细节。