上一篇粗略的介紹了一下netty,本篇將詳細介紹Netty的伺服器的啟動過程。
ServerBootstrap
看過上篇事例的人,可以知道ServerBootstrap
是Netty服務端啟動中扮演著一個重要的角色。
它是Netty提供的一個服務端引導類,繼承自AbstractBootstrap
。
ServerBootstrap
主要包括兩部分:bossGroup
和workerGroup
。其中bossGroup
主要用於繫結埠,接收來自客戶端的請求,接收到請求之後,就會把這些請求交給workGroup
去處理。就像現實中的老闆和員工一樣,自己開個公司(繫結埠),到外面接活(接收請求),使喚員工幹活(讓worker去處理)。
埠繫結
埠繫結之前,會先check引導類(ServerBootstrap)的bossGroup和workerGroup有沒有設定,之後再呼叫doBind。
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化並註冊一個channel,並將chanelFuture返回
final ChannelFuture regFuture = initAndRegister();
// 得到實際的channel(初始化和註冊的動作可能尚未完成)
final Channel channel = regFuture.channel();
// 發生異常時,直接返回
if (regFuture.cause() != null) {
return regFuture;
}
// 當到這chanel相關處理已經完成時
if (regFuture.isDone()) {
// 到這可以確定channel已經註冊成功
ChannelPromise promise = channel.newPromise();
// 進行相關的繫結操作
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 註冊一般到這就已經完成,到以防萬一
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 {
// 修改註冊狀態為成功(當註冊成功時不在使用全域性的executor,使用channel自己的,詳見 https://github.com/netty/netty/issues/2586)
promise.registered();
// 進行相關的繫結操作
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
複製程式碼
上面的程式碼主要有兩部分:初始化並註冊一個channel和繫結埠。
初始化並註冊一個channel
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 生產各新channel
channel = channelFactory.newChannel();
// 初始化channel
init(channel);
} catch (Throwable t) {
if (channel != null) {
// 註冊失敗時強制關閉
channel.unsafe().closeForcibly();
// 由於channel尚未註冊好,強制使用GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 註冊channel
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
複製程式碼
channel的初始化方法:
void init(Channel channel) throws Exception {
// 獲取bossChannel的可選項Map
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
// 獲取bossChannel的屬性Map
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();
// 設定worker的相關屬性
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();
// 新增handler到pipeline
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// 通過EventLoop將ServerBootstrapAcceptor到pipeline中,保證它是最後一個handler
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
複製程式碼
channel的註冊方法,最終是呼叫doRegister,不同的channel有所不同,下面以Nio為例:
protected void doRegister() throws Exception {
boolean selected = false;
for (; ; ) {
try {
// 直接呼叫java的提供的Channel的註冊方法
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
複製程式碼
繫結埠
最終呼叫的是NioServerSocketChannel的doBind方法。
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
複製程式碼
到這就完成了netty服務端的整個啟動過程。
文中帖的程式碼註釋全在:github.com/KAMIJYOUDOU… , 有興趣的童鞋可以關注一下。