2021SC@SDUSC
一、前言
在之前的博客中,大致分析了netty的NioEventLoopGroup的创建过程,在这篇博客中,将会分析ServerBootstrap的绑定方法,这是服务器端的方法。
二、ServerBootstrap类
ServerBootstrap是netty的服务器启动的引导类。
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
图为ServerBootstrap的继承体系。
在之前博客中的服务器-客户端通信的实例中,服务器bootstrap是通过无参构造方式创建的。
//netty的引导程序
ServerBootstrap bootstrap = new ServerBootstrap();
public ServerBootstrap() { }
可以看到,在netty中,ServerBootstrap的无参构造方法没有任何实现,之后,通过该对象的其它方法来指定一些配置,包括线程模型、Nio的channel类型、端口号等。本片博客主要会分析ServerBootstrap的bind方法,也就是绑定端口。
三、bind
在示例代码中,调用了参数为int类型的bind()方法,其具体实现是
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
可以看到,以传入的参数创建一个InetSocketAddress类的对象,顺便InetSocketAddress是SocketAddress类的子类,然后,再调用另外一个bind方法。该方法是
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
在该方法中,实现调用了validate()方法,其作用是判断group、channelFactory、childHandler以及childGroup是否为空,如果是null,抛出异常。以上这些属性分别在group、channel、childHandler方法中指定。
//ServerBootstrap中的validate方法
@Override
public ServerBootstrap validate() {
super.validate();
if (childHandler == null) {
throw new IllegalStateException("childHandler not set");
}
if (childGroup == null) {
logger.warn("childGroup is not set. Using parentGroup instead.");
childGroup = config.group();
}
return this;
}
//AbstractBootstrap中地validate方法
public B validate() {
if (group == null) {
throw new IllegalStateException("group not set");
}
if (channelFactory == null) {
throw new IllegalStateException("channel or channelFactory not set");
}
return self();
}
通过的话,返回到bind中,接着判断localAddress是否为空,如果是null,抛出空指针异常;通过的话,去执行doBind方法。
总之,在bind方法中,首先创建了InetSocketAddress类的对象,接着对一些变量进行判断,真正的绑定工作则要等到doBind方法中来完成。实际上,在netty源码中,存在许多的do开头的方法,而这些方法往往是负责实际内容的。
四、doBind
bind和doBind其实是AbstractBootstrap的方法。
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的创建,而之后通过regFuture获得了这个channel。
之后,先判断regFuture.cause()是否为空值,如果不是null,意味着在initAndRegister的过程中出现异常,直接返回。注意,在netty的代码中,往往是异步执行的,也就是说,有返回值并不等于执行过程已经完成了,所以,是否出现异常,应该等待之后判断。
接着,regFuture.isDone()判断是否执行完成,如果完成,调用doBind0()方法,增加一个监听器,等待完成,之后,再判断是否出现异常,如果没有,调用doBind0()方法。
关于doBind0方法,也会在下一篇博客中分析。
五、总结
在本篇博客中,分析了netty的服务器端绑定端口的过程,其中,包括initAndRegister和doBind0方法,有很多地方没有分析,只是简单地分析了过程,发现在bind方法中,只完成了一些基本过程,而实际地逻辑则要到doBind方法中完成,此外,bind、dobind、vaildate等方法其实是高层次地抽象方法,所以,具体实现可以由用户自定义。
此外,在netty地学习中,已经不止一次地出现了异步调用方法,这种方法的返回值往往是Future类型,也就是说,这类方法往往在执行过程中开启一个线程来完成具体逻辑,而返回一个Future对象,允许调用者在需要使用真正需要的数据时,可以通过Future对象获取。
但是,应该注意到,因为是异步调用的原因,在实际编写代码时,应该考虑到需要数据时,存在三种可能,一是,方法已经完成,数据已经存在;二是,方法执行过程中出现异常,这种时候,jdk的Future类型不能很好地处理;三是,方法还没有完成,在doBind方法中,添加一个监听器,等待方法完成,再接着处理接下来的逻辑。