Netty流程细讲之ServerBootStrap启动
引言
在Netty的引导程序中,启动一个服务端应用是一个十分简单的事情。配置链接类对象,配置子类初始化ChannelHandler,再调用bind方法绑定端口号,一个服务端应用就启动完毕了,接着只需要等待客户端发送连接请求,程序就能自动为我们完成客户端接入。看着是很简单的过程,Netty在背后却是做了相当多的工作,本文就以ServerBootStrap启动的时序动作为分析入手点,剖析在引导程序启动中,涉及到的具体代码内容。
总体时序流程
使用ServerBootStrap的bind方法执行对端口的监听,这里面涉及到好几个步骤,简单而言包括有:
- 按照给定的
Channel类,实例化一个对象。 - 将实例化的
Channel对象注册到EventLoop上。 - 设置
Channel的绑定监听地址,并且更新Channel对象的SelectionKey的事件关注集。
整体的时序如下

可以看到,一个完整的初始化动作内容很多,并且很多都是委托给其他类完成,下面按照三个大步骤进行区分,来逐一分析。
初始化Channel对象
ServerBootStrap使用bind方法绑定端口,其内部首先是执行Channel初始化方法,首先来看下方法的跳转流程,如下
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
//...省略其他代码
}
最终代码执行到initAndRegister方法,该方法内部完成了2个职责:1)初始化Channel对象;2)将Channel对象注册到EventLoop上。initAndRegister方法如下
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
//。。。省略代码,异常处理逻辑:channel不为null则关闭;返回一个代表失败的future对象
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
初始化工作由方法AbstractBootstrap#init方法完成,该方法是一个抽象方法,在ServerBootStrap中的实现如下
void init(Channel channel) throws Exception {
//。。。省略相关代码,主要是设置属性和配置项到Channel中,分别通过channel.attr(key).attr(attr)和channel.config().setOption完成
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
//。。。省略相关代码,主要是获取设置的客户端通道的配置对象和属性对象,局部变量名分为是currentChildOptions和currentChildAttrs
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));
}
});
}
});
}
唯一重要的部分就是向Channel的pipeline中添加一个匿名的ChannelHandler,即ChannelInitializer的匿名子类。这边先解释下ChannelInitializer这个类的原理。
ChannelInitializer
ChannelInitializer是一个继承了ChannelInboundHandlerAdapter的抽象类。其主要重写了2个方法:channelRegistered和handlerAdded。两个方法的重写思路基本一致,且通过互斥手段避免其方法内容的重复执行;在大多数情况下,只有handlerAdded方法中的内容可以成功执行,因此下面我们以重写的handlerAdded进行分析,其重写内容如下
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//Netty中的默认实现保证了当handlerAdded方法被调用时,通道已经注册到了EventLoop上。
if (ctx.channel().isRegistered()) {
if (initChannel(ctx)) {
removeState(ctx);
}
}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.add(ctx)) { // Guard against re-entrance.
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
}
return true;
}
return false;
}
handlerAdded主要的作用就是触发ChannelInitializer#initChannel(ChannelHandlerContext)方法,首先是通过一个防御性的编程,避免初始化动作并发执行,也就是initMap.add(ctx)这个方法调用的作用,initMap是一个并发安全的Set,集合中的元素为具体的ChannelHandlerContext对象。
接着调用抽象方法ChannelInitializer#initChannel(C)。该抽象方法由子类重写,在入门篇的示例中我们介绍过,一般这个重写的方法就是用来向pipeline中添加合适的处理器用于后续的业务处理。
当抽象的initChannel方法之后完毕后,将自身从pipeline中移除。由此可以看出,这个ChannelInitializer是一个一次性消耗品,当通道被注册到EventLoop上后,通过其子类重写的initChannel抽象方法完成向pipeline添加合适的处理器后,就从pipeline中删除,不再参与后续的流程,也很符合其“初始化器”的身份定位。
最终,ChannelInitializer#initChannel(ChannelHandlerContext)成功完成对应通道的初始化工作后,从initMap删除自身的ChannelHandlerContext对象,防止集合无限变大。因为initMap的作用是防止并发执行,因此执行成功后,可以安全删除其中的元素。
最后,还有一点需要解释的是,ChannelInitializer是一个共享的处理器,从其实现也能看出,其不持有不安全的实例属性,因此可以安全的并发应用于多个通道。在实际当中,我们常常通过ServerBootStrap#handler(ChannelHandler)传入一个ChannelInitializer对象,该对象就是被并发的应用在每一个接入进来的客户端Channel上的。
通过ChannelInitializer向pipeline添加ServerBootstrapAcceptor
书接上文,在初始化Channel的环节,我们向通道添加了一个ChannelInitializer的实现子类,其目的在于当Channel注册到EventLoop上时,向pipeline添加ServerBootstrapAcceptor,而这才是真正用于处理客户端接入请求业务逻辑的处理器。ServerBootstrapAcceptor也是一个入站处理器,但是其具体的作用,我们这边先按下不表,待到后文用到时再细说。添加完成之后,Channel的初始化工作即告完成。
注册Channel对象到EventLoop上
从initAndRegister方法可知,当初始化完成后,接下来就是将Channel对象注册到EventLoopGroup上。该职责由NioEventLoopGroup继承的父类方法MultithreadEventLoopGroup#register(Channel)完成。该方法内部通过next调用从NioEventLoop数组中选择一个实例,执行对应的register方法,该方法最终委托到了SingleThreadEventLoop#register(Channel)这个具体的实现上。其实现如下
public ChannelFuture register( Channel channel )
{
return(register( new DefaultChannelPromise( channel, this ) ) );
}
public ChannelFuture register( final ChannelPromise promise )
{
ObjectUtil.checkNotNull( promise, "promise" );
promise.channel().unsafe().register( this, promise );
return(promise);
}
Netty将一些它认为比较高阶的API调用都封装在自定义的Unsafe类中,以提醒开发者不要直接使用这些方法;这些方法都是Netty内部自行使用的。Unsafe的实例由抽象方法AbstractChannel#newUnsafe提供,这里的Channel类型是NioServerSocketChannel,对这个抽象方法的实现是来自其父类方法AbstractNioMessageChannel#newUnsafe,得到的实例对象是AbstractNioMessageChannel.N,其类图如下
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
<p> 通过本专刊的学习,对网络开发所需掌握的基础理论知识会更加牢固,对网络应用涉及的线程模型,设计模式,高性能架构等更加明确。通过对Netty的源码深入讲解,使得读者对Netty达到“知其然更之所以然”的程度。在遇到一些线上的问题时,具备了扎实理论功底的情况,可以有的放矢而不会显得盲目。 本专刊购买后即可解锁所有章节,故不可以退换哦~ </p> <p> <br /> </p>
顺丰集团工作强度 369人发布