本文主要对Netty中Server端启动进行分析,分析Server端是如何绑定端口,初始化Selector,启动NioEventLoop,并最终实现Reactor模式的。
首先看一段经典的Server启动代码。可以看到该过程中实例化了两个EventLoopGroup县城组:bossGroup、workerGroup。可以看到这个Server采用的是Reactor的多线程模式。创建的SimpleNettyServerHandler是通过childHandler()方法加入到workerGroup线程去执行的。
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new SimpleNettyServerHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
1. bind()入口
下面就以serverBootstrap.bind(port)为入口,进一步分析实际Server的启动流程的,直接进入到doBind(final SocketAddress localAddress)方法:
1.会调用initAndRegister()
初始化并注册一个Channel,此处的Channel是一个NioServerSocketChannel,该方法返回一个ChannelFuture。
2.判断ChannelFuture是否已经执行完成,如果执行完成会调用doBind0()
方法;如果没有执行完成,也会通过添加监听器等待执行完成后再调用doBind0()
方法。
因此,重点就落在initAndRegister()
和doBind0()
这两个方法上来了。
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) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
1.1 initAndRegister()分析
分析一下initAndRegister()
方法。通过函数名也大致可以看出该方法工作分为两部分:init(Channel channel)
和register
。
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);
}
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;
}
1.1.1 init过程分析
看一下init(Channel channel)
的实现。首先对Channel设置了ChannelOption和AttributeKey,然后会执行最重要的一步:在Channel的pipeline中添加ChannelHandler。具体ChannelPipeline的讲解会在单独的文章,可以看到该ChannelHandler的最终实现是一个ServerBootstrapAcceptor
,通过名字就可以看出这是一个专门做Acceptor的handler,具体对它的分析可以放在文章的后边。值得注意的是给pipeline添加ServerBootstrapAcceptor的过程是通过ch.eventLoop().execute(new Runnable()
执行的,ch.eventLoop()
返回实际就是NioEventLoop(具体可见:),所以实际是通过NioEventLoop中执行非I/O任务执行的。
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
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));
}
});
}
});
}
1.1.2 register分析
通过分析上边的init方法,当前的阶段是Channel已经初始化好了,Channel对应的Pipeline也已经初始化完成。但是这个Channel与Reactor线程之间还没有建立联系,下边的register的过程就是将这个Channel在NioEventLoop线程上进行注册。
注册的核心代码在AbstractChannel$AbstractUnsafe.register
方法中,下边的这段堆栈信息展示了是如何调到这个方法的:
"main@1" prio=5 tid=0x1 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:456)
at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80)
at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:74)
at io.netty.channel.MultithreadEventLoopGroup.register(MultithreadEventLoopGroup.java:86)
at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:332)
at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:283)
at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:279)
at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:254)
at com.zhangyk.server.SimpleNettyServer.bind(SimpleNettyServer.java:35)
at com.zhangyk.server.SimpleNettyServer.main(SimpleNettyServer.java:50)
register方法的实现非常简单,会将最终的实际执行封装在方法register0(ChannelPromise promise)
中,然后根据线程状态决定是在当前线程执行还是放在ch.eventLoop().execute(new Runnable()执行。
1)执行doRegister()
,这是最核心的一步,具体的实现在类io.netty.channel.nio. AbstractNioChannel
中,后边有贴源码实现,代码中可以看到会将javaChannel注册到NioEventLoop的selector上,这样NioEventLoop的selector就可以监听到Channel的I/O事件变化。
2)执行Channel对应Pipeline的fireChannelRegistered()
方法
3)根据是否是firstRegister以及isAutoRead()决定是否执行Pipeline的fireChannelActive()
方法和beginRead()
方法
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) {
.......
}
}
public abstract class AbstractNioChannel extends AbstractChannel {
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;
}
}
}
}
}
至此,可以看到,我们已经创建并初始化了NioServerSocketChannel,并且将该Channel注册到了NioEventLoop的Selector上。实际上,NioEventLoop的Loop循环已经可以开始监听ServerChannel的I/O事件了。
1.2 dobind分析
由上所知,initAndRegister()之后,Channel已经初始化好,并且对应的Selector都已经注册监听,NioEventLoop也已经开始Loop循环。现在还需要将该channel与我们所要监听的port端口进行绑定。可以看到,端口绑定的操作也是通过channel.eventLoop().execute
执行的。
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());
}
}
});
}
2. 启动状态图
此处贴用别人blog的一张图来表示一下。启动的操作一部分由Main线程完成,另一部分由bossGroup线程(即NioEventLoop线程)完成。
NioEventLoop线程除了执行I/O任务(第二个),还执行了doRegister()、doBind()、finishPipeline()这三个非I/O任务。
3. ServerBootstrapAcceptor类分析
通过上边的分析,我们知道ServerBootstrapAcceptor
是Acceptor Pipeline中的一个handler。由它负责对NioServerChannel上发生I/O事件进行处理。譬如,当NioServerChannel上有一个新的连接请求时,会由bossGroup线程的I/O任务Selector监听到(具体可参见NioEventLoop中I/O任务的分析过程),并通过调用 processSelectedKeys()方法,并最终执行到ServerBootstrapAcceptor
的channelRead(ChannelHandlerContext ctx, Object msg)
方法。
分析一下channelRead(ChannelHandlerContext ctx, Object msg)
方法,参数里边msg实际会是通过accept与客户端建立的一个子连接child
。具体的实行过程:
1.给这个子连接child
的pipeline 增加Handler,这个Handler就是文章最开始那段代码中通过childHandler添加的那个Handle。代码及child.pipeline().addLast(childHandler)
2.给子连接设定一些属性childOptions、AttributeKey等
3.将子连接child
注册到childGroup中.后续关于该子连接的一些I/O事件及其执行会在childGroup的EventLoop中。
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
private final EventLoopGroup childGroup;
private final ChannelHandler childHandler;
private final Entry<ChannelOption<?>, Object>[] childOptions;
private final Entry<AttributeKey<?>, Object>[] childAttrs;
private final Runnable enableAutoReadTask;
ServerBootstrapAcceptor(
final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
this.childGroup = childGroup;
this.childHandler = childHandler;
this.childOptions = childOptions;
this.childAttrs = childAttrs;
// Task which is scheduled to re-enable auto-read.
// It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
// not be able to load the class because of the file limit it already reached.
//
// See https://github.com/netty/netty/issues/1328
enableAutoReadTask = new Runnable() {
@Override
public void run() {
channel.config().setAutoRead(true);
}
};
}
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
private static void forceClose(Channel child, Throwable t) {
child.unsafe().closeForcibly();
logger.warn("Failed to register an accepted channel: {}", child, t);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
......
}