Bootstrap
Bootstrap
主要包含兩個部分,一個是伺服器地址的解析器組AddressResolverGroup
,另一個是用來工作的EventLoopGroup
。
EventLoopGroup
負責出人EventLoop
,AddressResolverGroup
負責給EventLoop
解析伺服器地址。
客戶端連線遠端伺服器
連線遠端伺服器,會先check引導類(Bootstrap)的group有沒有設定以及生成channel的工廠,之後再呼叫doResolveAndConnect方法。
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
// 初始化並註冊一個channel,並將chanelFuture返回
final ChannelFuture regFuture = initAndRegister();
// 得到實際的channel(初始化和註冊的動作可能尚未完成)
final Channel channel = regFuture.channel();
// 當到這chanel相關處理已經完成時
if (regFuture.isDone()) {
// 連線失敗直接返回
if (!regFuture.isSuccess()) {
return regFuture;
}
// 解析伺服器地址並完成連線動作
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
// 註冊一般到這就已經完成,這裡以防萬一
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
// 新增一個監聽器
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// Directly obtain the cause and do a null check so we only need one volatile read in case of a
// failure.
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
// 修改註冊狀態為成功(當註冊成功時不在使用全域性的executor,使用channel自己的,詳見 https://github.com/netty/netty/issues/2586)
promise.registered();
// 進行相關的繫結操作
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
複製程式碼
和ServerBootstrap
一樣,Bootstrap
也要先去初始化和註冊channel,註冊的方法和ServerBootstrap
相同,初始化channel的方法有所區別。
初始化channel
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
// 獲取channel的可選項Map
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
// 獲取channel的屬性Map
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e : attrs.entrySet()) {
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
}
}
複製程式碼
客戶端channel的初始化和服務端相比要簡單很多,只需要設定一些相關資訊即可。
channel處理好之後,客戶端就會去連線伺服器。
連線伺服器
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
// 解析器不知道怎麼處理這個伺服器地址或者已經處理過了
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
doConnect(remoteAddress, localAddress, promise);
return promise;
}
// 解析伺服器地址
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
// 解析完成時
if (resolveFuture.isDone()) {
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
// 解析失敗直接關閉channel
channel.close();
promise.setFailure(resolveFailureCause);
} else {
// 解析成功開始連線
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
// 解析沒完成時等待解析完成
resolveFuture.addListener(new FutureListener<SocketAddress>() {
@Override
public void operationComplete(Future<SocketAddress> future) throws Exception {
if (future.cause() != null) {
// 解析失敗直接關閉channel
channel.close();
promise.setFailure(future.cause());
} else {
// 解析成功開始連線
doConnect(future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable cause) {
promise.tryFailure(cause);
}
return promise;
}
複製程式碼
當伺服器地址是IP的時候,直接連線,如果是域名之類的,會先解析出伺服器的IP地址,然後再進行連線。域名解析直接使用的java.net.InetAddress的getByName方法,而連線的方法呼叫的是sun.nio.ch.SocketChannelImpl的connect方法。
文中帖的程式碼註釋全在:github.com/KAMIJYOUDOU… , 有興趣的童鞋可以關注一下。
本篇到此結束,如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【貳級天災】,查閱更多精彩歷史!!!