BootStrap在netty的應用程序中負責引導服務器和客戶端。netty包含了兩種不同類型的引導:
- 使用服務器的ServerBootStrap,用于接受客戶端的連接以及為已接受的連接創建子通道。
- 用于客戶端的BootStrap,不接受新的連接,并且是在父通道類完成一些操作。
本文主要分析ServerBootstarp相關源碼。
Bootstrap類繼承圖:
1、ServerBootstarap啟動流程分析
1.1、ServerBootstarap簡單啟動代碼
EventLoopGroup bossGroup = new NioEventLoopGroup(1);//用于接受accept事件的group serverSocketChannel
EventLoopGroup group = new NioEventLoopGroup(); //用于真正讀寫事件的group socketChannel
try {
//create ServerBootstrap instance
ServerBootstrap b = new ServerBootstrap(); //啟動裝載器,用于轉載配置
//Specifies NIO transport, local socket address
//Adds handler to channel pipeline
//使用NIO通道
//保持連接,如果不設置則一次通訊后自動斷開
b.group(bossGroup, group).channel(NioServerSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true).localAddress(port)
.handler(new SimpleServerHandler())//服務端handler
.childHandler(new ChannelInitializer<Channel>() { //channel使用的handler
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler2(), new EchoServerHandler());
}
});
//Binds server, waits for server to close, and releases resources
ChannelFuture f = b.bind().sync();
System.out.println(EchoServer.class.getName() + "started and listen on ?" + f.channel().localAddress());
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
主要處理說明:
(1)、新建NioEventLoopGroup類型的bossGroup和group。bossGroup主要處理服務端接收客戶端連接處理,group主要處理讀寫等I/O事件及任務等;
(2)、創建ServerBootstrap,其主要對一些處理進行代理,如bind()等操作,其是其他類的一個簡單門面;
(3)、channel()方法設置服務端的ServerSocketChannel實現類,本處實現類為NioServerSocketChannel。
(4)、option()方法設置Channel的相關選項,具體查看ChannelOption中的定義;
(5)、localAddress()設置服務端綁定的本地地址及端口;
(6)、handler()設置服務端的對應Channel的Handler;
(7)、childHandler()設置子連接的Channel的Handler;
(8)、bind()及sync()綁定本地地址并同步返回綁定結果;
1.2、bind()調用流程
bind()調用流程圖:
(1)、調用ServerBootstrap.bind():應用調用ServerBootstrap的bind()操作;
(2)、調用AbstractBootstrap.bind():調用doBind()對進行bind操作;
(3)、調用AbstractBootstrap.initAndRegister():利用ChannelFactory.newChannel()實例化NioServerSocketChannel;
(4)、調用ServerBootstrap.init():對NioServerSocketChannel進行初始化,主要操作如設置Channel相關的選項及屬性、設置ChannelHandler為ServerBootstrapAcceptor等,ServerBootstrapAcceptor為inbound類型的ChannelHandler,其為ServerBootstrap的內部類,其主要實現ChannelRead()操作,將客戶端的連接注冊到EventLoopGroup的EventLoop中。
(5)、調用NioEventLoop.register():將NioServerSocketChannel注冊到bossGroup中。
(6)、調用AbstractBootstrap.doBind0:將實際的bind操作以任務的形式添加到bossGroup的EventLoop中。
(7)、調用NioServerSocketChannel.bind():在EventLoop中以任務的形式調用此方法進行實際的bind()操作。
2、主要方法源碼分析
2.1、doBind()源碼分析
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;
}
}
主要流程處理:
- 調用initAndRegister()初始化Channel并將其注冊到bossGroup中的NioEventLoop中;
- 若注冊成功,則調用doBind0()進行實際的bind操作;
- 若還未注冊,則創建注冊結果的監聽器及doBind0()的異步結果,若Channel注冊成功,則在結果監聽器中進行doBind0()操作,并將bind()異步結果這種為成功;否則將在監聽器中設置異步結果為失??;
2.2、initAndRegister()源碼分析
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();
}
}
// 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;
}
主要處理流程:
- 通過ChannelFactory新創建一個Channel;
- 調用ServerBootstrap的init()方法對Channel進行初始化;
2.3、init()源碼分析
init()源碼:
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(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}
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));
}
});
}
});
}
主要處理流程:
- 如果設置了Channel選項,則調用setChannelOptions()對Channel進行選項設置;
- 如果設置了屬性,則將對應屬性設置為Channel的屬性;
- 設置子Channel的選項及屬性;
- 初始化NioServerSocketChannel的ChannelHandler為ServerBootstrapAcceptor,ServerBootstrapAcceptor為inbound類型的ChannelHandler,其主要功能是將已經接受連接的子Channel注冊到workerGroup的NioEventLoop中;
2.4、doBind0()源碼分析
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());
}
}
});
}
主要處理流程:
- 將NioServerSocketChannel.bind()操作封裝為任務,并將任務提交給其對應的EventLoop進行處理;
2.5、ServerBootstrapAcceptor源碼分析
2.5.1、ServerBootstrapAcceptor類繼承圖:
ServerBootstrapAcceptor為NioServerSocketChannel的ChannelHandler,其類型為Inbound類型;
2.5.2、ServerBootstrapAcceptor源碼:
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 {
final ChannelConfig config = ctx.channel().config();
if (config.isAutoRead()) {
// stop accept new connections for 1 second to allow the channel to recover
// See https://github.com/netty/netty/issues/1328
config.setAutoRead(false);
ctx.channel().eventLoop().schedule(enableAutoReadTask, 1, TimeUnit.SECONDS);
}
// still let the exceptionCaught event flow through the pipeline to give the user
// a chance to do something with it
ctx.fireExceptionCaught(cause);
}
}
ServerBootstrapAcceptor主要實現了以下方法:
(1)、channelRead():設置子連接的ChannelHandler、設置子連接的Channel選項,設置子連接的Channel屬性,將子連接注冊的child對應的EventLoop中(即workerGroup的EventLoop中);
(2)、exceptionCaught():若ServerSocketChannel在accept子連接時拋出異常,若ServerSocketChannel的autoRead為true,則設置其為false,即不允許自動接收客戶端連接,并延遲1s后再設置其為true,使其允許自動接收客戶端連接;
相關閱讀:
Netty源碼愫讀(一)ByteBuf相關源碼學習 【http://www.lxweimin.com/p/016daa404957】
Netty源碼愫讀(二)Channel相關源碼學習【http://www.lxweimin.com/p/02eac974258e】
Netty源碼愫讀(三)ChannelPipeline、ChannelHandlerContext相關源碼學習【http://www.lxweimin.com/p/be82d0fcdbcc】
Netty源碼愫讀(四)ChannelHandler相關源碼學習【http://www.lxweimin.com/p/6ee0a3b9d73a】
Netty源碼愫讀(五)EventLoop與EventLoopGroup相關源碼學習【http://www.lxweimin.com/p/05096995d296】
參考書籍:
《Netty權威指南》第二版