本文主要對Netty中Server端啟動進行分析甚颂,分析Server端是如何綁定端口刁品,初始化Selector傀蚌,啟動NioEventLoop,并最終實現(xiàn)Reactor模式的脱衙。
首先看一段經(jīng)典的Server啟動代碼扛点。可以看到該過程中實例化了兩個EventLoopGroup縣城組:bossGroup岂丘、workerGroup陵究。可以看到這個Server采用的是Reactor的多線程模式奥帘。創(chuàng)建的SimpleNettyServerHandler是通過childHandler()方法加入到workerGroup線程去執(zhí)行的铜邮。
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.會調(diào)用initAndRegister()
初始化并注冊一個Channel松蒜,此處的Channel是一個NioServerSocketChannel,該方法返回一個ChannelFuture已旧。
2.判斷ChannelFuture是否已經(jīng)執(zhí)行完成秸苗,如果執(zhí)行完成會調(diào)用doBind0()
方法;如果沒有執(zhí)行完成运褪,也會通過添加監(jiān)聽器等待執(zhí)行完成后再調(diào)用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()
方法檀咙。通過函數(shù)名也大致可以看出該方法工作分為兩部分: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)
的實現(xiàn)璃诀。首先對Channel設(shè)置了ChannelOption和AttributeKey弧可,然后會執(zhí)行最重要的一步:在Channel的pipeline中添加ChannelHandler。具體ChannelPipeline的講解會在單獨的文章劣欢,可以看到該ChannelHandler的最終實現(xiàn)是一個ServerBootstrapAcceptor
棕诵,通過名字就可以看出這是一個專門做Acceptor的handler裁良,具體對它的分析可以放在文章的后邊。值得注意的是給pipeline添加ServerBootstrapAcceptor的過程是通過ch.eventLoop().execute(new Runnable()
執(zhí)行的校套,ch.eventLoop()
返回實際就是NioEventLoop(具體可見:)趴久,所以實際是通過NioEventLoop中執(zhí)行非I/O任務(wù)執(zhí)行的。
@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方法搔确,當(dāng)前的階段是Channel已經(jīng)初始化好了彼棍,Channel對應(yīng)的Pipeline也已經(jīng)初始化完成。但是這個Channel與Reactor線程之間還沒有建立聯(lián)系膳算,下邊的register的過程就是將這個Channel在NioEventLoop線程上進行注冊座硕。
注冊的核心代碼在AbstractChannel$AbstractUnsafe.register
方法中,下邊的這段堆棧信息展示了是如何調(diào)到這個方法的:
"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方法的實現(xiàn)非常簡單涕蜂,會將最終的實際執(zhí)行封裝在方法register0(ChannelPromise promise)
中华匾,然后根據(jù)線程狀態(tài)決定是在當(dāng)前線程執(zhí)行還是放在ch.eventLoop().execute(new Runnable()執(zhí)行。
1)執(zhí)行doRegister()
,這是最核心的一步机隙,具體的實現(xiàn)在類io.netty.channel.nio. AbstractNioChannel
中蜘拉,后邊有貼源碼實現(xiàn),代碼中可以看到會將javaChannel注冊到NioEventLoop的selector上有鹿,這樣NioEventLoop的selector就可以監(jiān)聽到Channel的I/O事件變化旭旭。
2)執(zhí)行Channel對應(yīng)Pipeline的fireChannelRegistered()
方法
3)根據(jù)是否是firstRegister以及isAutoRead()決定是否執(zhí)行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;
}
}
}
}
}
至此,可以看到葱跋,我們已經(jīng)創(chuàng)建并初始化了NioServerSocketChannel持寄,并且將該Channel注冊到了NioEventLoop的Selector上。實際上娱俺,NioEventLoop的Loop循環(huán)已經(jīng)可以開始監(jiān)聽ServerChannel的I/O事件了稍味。
1.2 dobind分析
由上所知,initAndRegister()之后荠卷,Channel已經(jīng)初始化好模庐,并且對應(yīng)的Selector都已經(jīng)注冊監(jiān)聽,NioEventLoop也已經(jīng)開始Loop循環(huán)∮鸵耍現(xiàn)在還需要將該channel與我們所要監(jiān)聽的port端口進行綁定掂碱。可以看到验庙,端口綁定的操作也是通過channel.eventLoop().execute
執(zhí)行的顶吮。
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. 啟動狀態(tài)圖
此處貼用別人blog的一張圖來表示一下社牲。啟動的操作一部分由Main線程完成粪薛,另一部分由bossGroup線程(即NioEventLoop線程)完成。
NioEventLoop線程除了執(zhí)行I/O任務(wù)(第二個)搏恤,還執(zhí)行了doRegister()违寿、doBind()湃交、finishPipeline()這三個非I/O任務(wù)。
3. ServerBootstrapAcceptor類分析
通過上邊的分析藤巢,我們知道ServerBootstrapAcceptor
是Acceptor Pipeline中的一個handler搞莺。由它負責(zé)對NioServerChannel上發(fā)生I/O事件進行處理。譬如掂咒,當(dāng)NioServerChannel上有一個新的連接請求時才沧,會由bossGroup線程的I/O任務(wù)Selector監(jiān)聽到(具體可參見NioEventLoop中I/O任務(wù)的分析過程),并通過調(diào)用 processSelectedKeys()方法绍刮,并最終執(zhí)行到ServerBootstrapAcceptor
的channelRead(ChannelHandlerContext ctx, Object msg)
方法温圆。
分析一下channelRead(ChannelHandlerContext ctx, Object msg)
方法,參數(shù)里邊msg實際會是通過accept與客戶端建立的一個子連接child
孩革。具體的實行過程:
1.給這個子連接child
的pipeline 增加Handler岁歉,這個Handler就是文章最開始那段代碼中通過childHandler添加的那個Handle。代碼及child.pipeline().addLast(childHandler)
2.給子連接設(shè)定一些屬性childOptions膝蜈、AttributeKey等
3.將子連接child
注冊到childGroup中.后續(xù)關(guān)于該子連接的一些I/O事件及其執(zhí)行會在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 {
......
}