netty作為客戶端從bootstrap啟動,作為服務(wù)端從ServerBootstrap,本文默認(rèn)傳輸層協(xié)議為TCP協(xié)議。
UML圖
如上圖所示,Bootstrap和ServerBoostrap都繼承自AbstractBootstrap.
Bootstrap
Bootstrap用于一個客戶端連接服務(wù)器,獲取一個channel,代碼如下
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
}
p.addLast(new DiscardClientHandler());
}
});
// Make the connection attempt.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
bootstrap的核心代碼是connect方法如下:
/**
* Connect a {@link Channel} to the remote peer.
*/
public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
validate();
return doResolveAndConnect(remoteAddress, config.localAddress());
}
doResolveAndConnect方法如下:
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
// 首先創(chuàng)建并初始化并注冊一個channel
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 如果注冊完成,channel已經(jīng)找到了自己的EventLoop/excutor,則直接調(diào)用
// doResolveAndConnect0解析域名和連接服務(wù)器
if (regFuture.isDone()) {
if (!regFuture.isSuccess()) {
return regFuture;
}
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
// 如果沒有注冊完成,則為注冊future增加一個listener,但是有個問題:
// 返回的connectFuture怎么辦?由于channel現(xiàn)在不一定有excutor,
// 所以你不能用channel.newPromise()去新建一個promise, 所以,就誕生了
// PendingRegistrationPromise, Future/listern模式我們后面會專門研究
// 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 {
// Direclty 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) {
// 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();
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
PendingRegistrationPromise 到底是什么鬼?其實就干一件事:如果注冊成功,其執(zhí)行線程是channel的執(zhí)行線程,如果注冊失敗,執(zhí)行線程是一個全局的執(zhí)行線程。
static final class PendingRegistrationPromise extends DefaultChannelPromise {
// Is set to the correct EventExecutor once the registration was successful. Otherwise it will
// stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications.
private volatile boolean registered;
PendingRegistrationPromise(Channel channel) {
super(channel);
}
void registered() {
registered = true;
}
@Override
protected EventExecutor executor() {
// 如果注冊完成了, 這個listener就是就是channel的enventLoop
if (registered) {
// If the registration was a success executor is set.
//
// See https://github.com/netty/netty/issues/2586
return super.executor();
}
// 否則就是全局的一個線程
// The registration failed so we can only use the GlobalEventExecutor as last resort to notify.
return GlobalEventExecutor.INSTANCE;
}
}
initAndRegister()方法在AbstractBootstrap中
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 首先產(chǎn)生一個channel
channel = channelFactory.newChannel();
// 初始化, 該方法在bootstrap中,這個方法里面就是為channel設(shè)置一些可選項和屬性
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);
}
// 注冊channel
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;
}
doResolveAndConnect0()方法解析遠(yuǎn)程域名并發(fā)起連接
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);
// 如果不支持解析,或已經(jīng)解析,直接連接
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
doConnect(remoteAddress, localAddress, promise);
return promise;
}
// 如果沒有解析,則現(xiàn)在解析
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
//解析完成,則馬上解析
if (resolveFuture.isDone()) {
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
// Failed to resolve immediately
channel.close();
promise.setFailure(resolveFailureCause);
} else {
// Succeeded to resolve immediately; cached? (or did a blocking lookup)
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
// Wait until the name resolution is finished.
// 解析沒有完成,則放在listener中連接
resolveFuture.addListener(new FutureListener<SocketAddress>() {
@Override
public void operationComplete(Future<SocketAddress> future) throws Exception {
if (future.cause() != null) {
channel.close();
promise.setFailure(future.cause());
} else {
doConnect(future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable cause) {
promise.tryFailure(cause);
}
return promise;
}
doConnect方法負(fù)責(zé)連接遠(yuǎn)程服務(wù)器,調(diào)用channel的connect方法完成,channel.connect()方法負(fù)責(zé)調(diào)用java的connetc方法。
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
總結(jié),Bootstrap中的方法風(fēng)味兩類:設(shè)置屬性和連接遠(yuǎn)程服務(wù)器,連接遠(yuǎn)程服務(wù)器的底層調(diào)用channel的連接,所以本質(zhì)上連接操作是在channel里面做的,bootstrap中的連接過程如下:
1: 創(chuàng)建一個channel
2: 初始化一個channel
3: 注冊一個channel到一個eventLoop中去,
4: 解析遠(yuǎn)程域名,但是未必能夠正確解析
5: 調(diào)用channel.connect連接遠(yuǎn)程服務(wù)器