引導
在Netty中,有兩種引導,一是Bootstrap,用于引導客戶端或者無連接服務器;另一種便是ServerBootstrap,用于引導面向連接的服務器。Bootstrap整個類層次如下圖所示,本文將依次分析AbstractBootstrap、Bootstrap和ServerBootstrap。
AbstractBootstrap類
AbstractBootstrap類的javadoc說明如下:
AbstractBootstrap
is a helper class that makes it easy to bootstrap aChannel
. It support method-chaining to provide an easy way to configure theAbstractBootstrap
.
When not used in aServerBootstrap
context, thebind()
methods are useful for connectionless transports such as datagram (UDP).
上述文字表明:AbstractBootstrap類支持方法的鏈式調用,當不使用ServerBootstrap時,bind方法可以用于無連接的協議如UDP等,這與日常用法相一致。
成員變量和構造函數
volatile EventLoopGroup group;
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory;
private volatile SocketAddress localAddress;
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
private volatile ChannelHandler handler;
AbstractBootstrap() {
// Disallow extending from a different package.
}
AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
group = bootstrap.group;
channelFactory = bootstrap.channelFactory;
handler = bootstrap.handler;
localAddress = bootstrap.localAddress;
synchronized (bootstrap.options) {
options.putAll(bootstrap.options);
}
synchronized (bootstrap.attrs) {
attrs.putAll(bootstrap.attrs);
}
}
以上代碼有以下幾點需要注意:
- 構造函數中直接訪問了另一個實例的私有變量,這種是被允許的,因為訪問控制是控制其他類是否能夠訪問本類的變量或者方法,具體可參閱Controlling Access to Members of a Class;
- 為什么要有兩個synchronized代碼塊呢?因為putAll方法的javadoc表明如果源map在被put到目的map的過程中被修改,這個過程是未定義的,具體可參閱官方文檔。
成員方法
AbstractBootstrap類是一種構建者模式(Builder)
- group方法設置了成員變量group;
- channel/channelFactory方法設置了成員變量channelFactory,用來創建Channel。區別在于channelFactory方法直接指定了工廠,channel則是利用類參數創建了ReflectiveChannelFactory實例然后接著調用了channelFactory方法。channelFactory方法的javadoc提到:
This method is usually only used if
channel(Class)
is not working for you because of some more complex needs. If yourChannel
implementation has a no-args constructor, its highly recommend to just usechannel(Class)
to simplify your code - localAddress方法設置了成員變量localAddress,這個方法有幾種重載的形式;
- option方法和attr方法相似,都可以使用null移除鍵;
- validate方法驗證group和channelFactory均不為null,這個方法會在bind方法中被調用。注意子類可以重寫該方法,但必須調用基類的方法,后續會看到Bootstrap和ServerBootstrap都重寫了該方法,加入了自己額外的驗證。《Netty實戰》8.2.2節有以下描述,這正是父類和子類validate方法的作用;
在引導的過程中,在調用bind()或者connect()方法之前,必須調用以下方法來設置所需的組件:
group();
channel()或者channelFactory();
handler().
如果不這樣做,則將會導致IllegalStateException。對handler()方法的調用尤其重要,因為它需要配置好ChannelPipeline。 - bind方法比較復雜,下面詳細分析一下。
bind方法
bind方法會在內部調用doBind方法,首先會調用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) {
// 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方法
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();
}
}
return regFuture;
}
abstract void init(Channel channel) throws Exception;
initAndRegister方法是一個模板方法
- 利用channelFactory新建通道,以前述的ReflectiveChannelFactory為例,其newChannel方法會根據傳入的Channel類型調用對應的無參構造函數返回新建的通道;
public ReflectiveChannelFactory(Class<? extends T> clazz) { if (clazz == null) { throw new NullPointerException("clazz"); } this.clazz = clazz; } @Override public T newChannel() { try { return clazz.getConstructor().newInstance(); } catch (Throwable t) { throw new ChannelException("Unable to create Channel from class " + clazz, t); } }
- 調用抽象的init方法初始化新建的通道,子類需要重寫該方法;
- 將新建的通道注冊到與該引導類關聯的EventLoopGroup上。
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());
}
}
});
}
doBind0方法使得在Channel綁定的EventLoop上執行具體的通道綁定操作。注意從doBind0被調用的位置可以看到其一定是在注冊操作完成之后被調用:
- doBind方法中if (regFuture.isDone()) 代碼塊內,這時注冊已經完成,不用去管是否成功,因為doBind0內部會判斷;
- doBind方法中的else代碼塊與上面類似。
Bootstrap類
Bootstrap類繼承了AbstractBootstrap類,新增加了remoteAddress和resolver成員變量,與之對應有remoteAddress和resolver成員方法。
成員方法
validate方法:前文提到Bootstrap會重寫AbstractBootstrap類的該方法,Bootstrap類除了調用基類的方法,還驗證了handler不為null;
-
init方法:為通道添加了配置的處理器,設置了通道的選項和屬性;
void init(Channel channel) throws Exception { ChannelPipeline p = channel.pipeline(); p.addLast(config.handler()); 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()) { channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } } }
-
connect方法:connect方法會在內部調用doResolveAndConnect方法完成解析遠程域名和連接的工作,整體流程與bind方法很相似,也是先初始化并注冊通道,接下來按注冊是否結束分情況討論,委托給了doResolveAndConnect0方法。
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.isDone()) { if (!regFuture.isSuccess()) { return regFuture; } return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise()); } 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 { // 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) { // 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; } }
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)) { // Resolver has no idea about what to do with the specified remote address or it's resolved already. doConnect(remoteAddress, localAddress, promise); return promise; } 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. 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; } 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); } }); }
ServerBootstrap類
ServerBootstrap類繼承了AbstractBootstrap類,新增加了childHandler、childGroup、childOptions和childAttrs成員變量,并增加了與之對應的成員方法。
成員方法
- validate方法:前文提到ServerBootstrap會重寫AbstractBootstrap類的該方法,ServerBootstrap類除了調用基類的方法,還驗證了childHandler和childGroup均不為null;
- 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(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)); } }); } }); }
ServerBootstrapAcceptor類
ServerBootstrapAcceptor類是ServerBootstrap類的私有靜態內部類,用于充當Reactor模式中的Acceptor角色,它繼承了ChannelInboundHandlerAdapter類:
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;
// 省略一些代碼
}
@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);
}
}
// 省略一些代碼
}
注意channelRead方法:
- final Channel child = (Channel) msg; 表明它的入站消息是一個通道;為什么是一個通道呢?請看Netty學習 - EventLoop。
- child.pipeline().addLast(childHandler); 接受連接后才將已連接通道的處理器(即ServerBootstrap的childHandler方法中的參數)添加到已連接通道的流水線上,并設置選項;
- 將已連接通道注冊到childGroup(從Reactor)上。
ServerBootstrap引導的結果是將ServerSocketChannel注冊到group變量(即所謂的bossGroup)表示的EventLoopGroup里的一個EventLoop上,即ServerBootstrapAcceptor只運行于一個EventLoop里。