在上一篇文章中,我们对Netty启动的流程做了一个概述
Netty源码解析(一)之Netty启动流程(4.1.47.Final)_benjam1n77的博客-优快云博客
这篇文章中,我将从源码出发,详细解析Netty服务器启动流程。(为了简洁,只贴出关键的源码部分)
一. ServerSocketChannel的初始化和注册
进入到该方法
ServerBootstrp.bind(int inetPort)
然后会进入AbstractBootstrap.doBind()方法中。
//AbstractBootstrap.java
//line 271-305
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;
}
}
这一节先讲ServerSocketChannel的初始化和注册,该逻辑在AbstractBootstrap.initAndRegister()方法中。首先channelFactory.newChannel()会通过反射创建一个NioServerSocketChannel实例,然后调用init(channel)进行初始化。初始化主要做了以下几件事情:
- 为ServerSocketChannel设置channelOption和attributes,这个channelOption和attributes是用户自己设置的。
- 为ServerSocketChannel添加一个ChannelInitializer,该Initializer会为ServerSocketChannel添加一个类型为ServerBootstrapAcceptor的Handler。
//AbstractBootstrap.java
//line 307-342
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
//ServerBootstrap.java
//line 131-163
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Map.Entry<ChannelOption<?>, Object>[] currentChildOptions;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
final Map.Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
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));
}
});
}
});
}
初始化完成后,进行ServerSocketChannel的注册。注册过程主要分为一下几步:
- 调用doRegister()方法,在该方法中终于见到了原生JDK NIO的方法调用了,将NioServerSocketChannel的java channel注册到Selector上,并将感兴趣的事件设置为0,即对任何IO事件都不感兴趣(在后续的过程中才会将interested ops设置为OP_ACCEPT)。
- NioServerSocketChannel的ChannelPipeline会触发一个handlerAdded事件,此时在上一步的初始化过程中所添加的ChannelInitializer的initChannel()方法会被触发,该方法会在ChannelPipeline中再添加一个名为ServerBootstrapAcceptor的Handler。
- ChannelPipeline再触发一个ChannelRegistered事件,这一步如果用户没有为NioServerSocketChannel添加额外的Handler的话则不会发生任何事情。
- 注意:在下面的代码中isActive()方法返回的是false,因为此时Channel只进行了初始化和注册,还未绑定端口,处于未激活状态,因此条件判断代码块中的pipeline.fireChannelActive()和beginRead()都不会被执行。
//AbstractChannel.java
//line 488-525
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);
}
}
//AbstractNioChannel.java
//line 376-395
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;
}
}
}
}
二. ServerSocketChannel的绑定
ServerSocketChannel完成初始化和注册后,进行端口的绑定,主要分为以下几步:
- 调用doBind()方法,通过原生的JDK NIO将java channel绑定到特定的端口上。
- ServerSocketChannel的ChannelPipeline触发一个channel active事件。
//AbstractChannel.java
//line 528-567
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
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);
}
//NioServerSocketChannel.java
//line 132-138
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
毫无疑问,上述过程中的第2步触发的channel active事件会传递给NioServerSocketChannel的Handler,此时NioServerSocketChannel有三个Handler:head->ServerBootstrapAcceptor->tail,ServerBootstrapAcceptor以及tail这两个handler的channelActive()方法都不会做任何事情,主要的逻辑在headHandler的channelActive()方法中,而这个方法最后是会调用到AbstractNioChannel的doBeginRead()方法,在该方法中,最终会将NioServerSocketChannel注册到Selector的感兴趣的事件设置为readInterestOp,即SelectionKey.OP_ACCEPT。
//DefaultChannelPipeline.java
//line 1397-1401
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
readIfIsAutoRead();
}
//DefaultChannelPipeline.java
//line 1420-1424
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
//AbstractNioChannel.java
//line 403-416
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
至此,NioServerSocketChannel就可以真正的开始接收客户端连接了。有小伙伴可能会好奇,在原生的JDK NIO中,服务器接收客户端连接时,应该是要在一个死循环中调用selector.select()才能接收到客户端的连接啊,怎么在Netty中看不到这一段代码?别急,其实这段代码在Netty中是有的,还记得我们的bossEventLoopGroup吗?这个EventLoopGroup中的线程在开启后就会在一个死循环中不停的调用selector.select()来查看是否有新的客户端连接,正如在上一篇文章中的这张图所示:
关于EventLoopGroup的详细的工作原理我将会在后续专门写一篇文章来介绍。