每日一诗
春未老,风细柳斜斜。试上超然台上望,半壕春水一城花。烟雨暗千家。
寒食后,酒醒却咨嗟。休对故人思故国,且将新火试新茶。诗酒趁年华。
——苏轼《望江南·超然台作》
前言
本文主要讲的是Netty服务端启动bind()源码。先来参考下之前写过的Netty demo代码:
package com.gupaoedu.vip.netty.tomcat.nio;
import com.gupaoedu.vip.netty.tomcat.nio.http.GPRequest;
import com.gupaoedu.vip.netty.tomcat.nio.http.GPResponse;
import com.gupaoedu.vip.netty.tomcat.nio.http.GPServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import java.io.FileInputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class GPTomcat {
private int port = 8080;
private ServerSocket server;
private Properties webxml = new Properties();
private Map<String, GPServlet> servletMapping = new HashMap<String, GPServlet>();
public static void main(String[] args) {
new GPTomcat().start();
}
//Tomcat的启动入口
private void start() {
//1、加载web.properties文件,解析配置
init();
//Boss线程
EventLoopGroup bossGroup = new NioEventLoopGroup();
//Worker线程
EventLoopGroup workerGroup = new NioEventLoopGroup();
//2、创建Netty服务端对象
ServerBootstrap server = new ServerBootstrap();
try {
//3、配置服务端参数
server.group(bossGroup, workerGroup)
//配置主线程的处理逻辑
.channel(NioServerSocketChannel.class)
//子线程的回调处理,Handler
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel client) throws Exception {
//处理回调的逻辑
//链式编程,责任链模式
//处理响应结果的封装
client.pipeline().addLast(new HttpResponseEncoder());
//用户请求过来,要解码
client.pipeline().addLast(new HttpRequestDecoder());
//用户自己的业务逻辑
client.pipeline().addLast(new GPTomcatHandler());
}
})
//配置主线程分配的最大线程数
.option(ChannelOption.SO_BACKLOG, 128)
//保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
//启动服务
ChannelFuture f = server.bind(this.port).sync();
System.out.println("GP Tomcat 已启动,监听端口是: " + this.port);
f.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private void init() {
try {
String WEB_INF = this.getClass().getResource("/").getPath();
FileInputStream fis = new FileInputStream(WEB_INF + "web-nio.properties");
webxml.load(fis);
for (Object k : webxml.keySet()) {
String key = k.toString();
if(key.endsWith(".url")){
//将 servlet.xxx.url 的 .url 替换,只剩下 servlet.xxx当成 servletName
String servletName = key.replaceAll("\\.url$","");
String url = webxml.getProperty(key);
//拿到Serlvet的全类名
String className = webxml.getProperty(servletName + ".className");
//反射创建Servlet的实例
GPServlet obj = (GPServlet) Class.forName(className).newInstance();
//将URL和Servlet建立映射关系
servletMapping.put(url,obj);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public class GPTomcatHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof HttpRequest){
HttpRequest req = (HttpRequest) msg;
GPRequest request = new GPRequest(ctx,req);
GPResponse response = new GPResponse(ctx,req);
String url = request.getUrl();
if(servletMapping.containsKey(url)){
servletMapping.get(url).service(request,response);
}else{
response.write("404 - Not Found!!");
}
}
}
}
}
整个netty步骤:
- 创建对象
- 配置参数
- 启动服务(bind方法)
本章我们讲解3.启动服务 的代码,前面的创建对象啊,配置参数啦,我们这篇先不讲,后面再讲,先宏观,再微观,能够帮助我们了解建立整个netty。这一句的代码其实就一句话,server.bind(int port):
//启动服务
ChannelFuture f = server.bind(this.port).sync();
本篇文章是源码的追踪与解析,会比较枯燥。
一、服务端启动核心路径总结
1.1 从网上p来的图
1.1.1 服务端时序图
1.1.2 Netty服务端创建时序图以及步骤
下面我们对Netty服务端创建的关键步骤和原理进行讲解。
步骤1:创建ServerBootstrap实例。ServerBootstrap是Netty服务端的启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层通过门面模式对各种能力进行抽象和封装,尽量不需要用户跟过多的底层API打交道,降低用户的开发难度。
我们在创建ServerBootstrap实例时,会惊讶的发现ServerBootstrap只有一个无参的构造函数,作为启动辅助类这让人不可思议,因为它需要与多个其它组件或者类交互。ServerBootstrap构造函数没有参数的根本原因是因为它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入Builder模式。《Effective Java》第二版第2条建议遇到多个构造器参数时要考虑用构建器,关于多个参数构造函数的缺点和使用构建器的优点大家可以查阅《Effective Java》,在此不再详述。
步骤2:设置并绑定Reactor线程池。Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop的数组。EventLoop的职责是处理所有注册到本线程多路复用器Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行。值得说明的是,EventLoop的职责不仅仅是处理网络I/O事件,用户自定义的Task和定时任务Task也统一由EventLoop负责处理,这样线程模型就实现了统一。从调度层面看,也不存在在EventLoop线程中再启动其它类型的线程用于异步执行其它的任务,这样就避免了多线程并发操作和锁竞争,提升了I/O线程的处理和调度性能。
步骤3:设置并绑定服务端Channel。作为NIO服务端,需要创建ServerSocketChannel,Netty对原生的NIO类库进行了封装,对应实现是NioServerSocketChannel。对于用户而言,不需要关心服务端Channel的底层实现细节和工作原理,只需要指定具体使用哪种服务端Channel即可。因此,Netty的ServerBootstrap方法提供了channel方法用于指定服务端Channel的类型。Netty通过工厂类,利用反射创建NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代码如下所示:
步骤4:链路建立的时候创建并初始化ChannelPipeline。ChannelPipeline并不是NIO服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler的执行。典型的网络事件如下:
- 链路注册;
- 链路激活;
- 链路断开;
- 接收到请求消息;
- 请求消息接收并处理完毕;
- 发送应答消息;
- 链路发生异常;
- 发生用户自定义事件。
步骤5:初始化ChannelPipeline完成之后,添加并设置ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形等。Netty同时也提供了大量的系统ChannelHandler供用户使用,比较实用的系统ChannelHandler总结如下:
- 系统编解码框架-ByteToMessageCodec;
- 通用基于长度的半包解码器-LengthFieldBasedFrameDecoder;
- 码流日志打印Handler-LoggingHandler;
- SSL安全认证Handler-SslHandler;
- 链路空闲检测Handler-IdleStateHandler;
- 流量整形Handler-ChannelTrafficShapingHandler;
- Base64编解码-Base64Decoder和Base64Encoder。
创建和添加ChannelHandler的代码示例如下:
步骤6:绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接,相关代码如下:
步骤7:Selector轮询。由Reactor线程NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合,相关代码如下:
步骤8:当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行ChannelHandler,代码如下:
步骤9:执行Netty系统ChannelHandler和用户添加定制的ChannelHandler。ChannelPipeline根据网络事件的类型,调度并执行ChannelHandler。
1.1.3 客户端时序图分析
步骤:
1.用户线程创建Bootstrap实例
2.创建处理客户端连接,I/O读写Reactor线程组NioEventLoopGroup
3.创建NioSocketChannel
4.创建默认的ChannelHandlerPipeline,用户调度和执行网络事件
5.异步发起TCP连接,如果成功将NioSocketChannel注册到多路复用选择器上,监听读操作位,用于数据读取和消息发送,如果失败,注册连接操作位到多路复用选择 器,等待连接结果
6.注册对应的网络监听状态位到多路复用选择器
7.由多路复用选择器轮询Channel,处理连接结果
8.如果连接成功,设置Future结果,发送连接成功事件,触发ChannelHandlerPipeline执行
9.由ChannelHandlerPipeline调度和执行系统和用户ChannelHandler
二、服务端channel的创建
2.1从bind()方法开始
我们点进去上面的bind()方法会进去到AbstractBootstrap#bind方法(为了让读者更容易找到源码,本篇文章不光会贴源码,也会附上相应的的源码图片):
public ChannelFuture bind(int inetPort) {
return this.bind(new InetSocketAddress(inetPort));
}
该方法有多个重载方法,但核心作用只有一个,就是将参数转为InetSocketAddress对象传给 --->bind方法.
通过端口号创建一个 InetSocketAddress
,然后继续bind:
public ChannelFuture bind(SocketAddress localAddress) {
this.validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
} else {
return this.doBind(localAddress);
}
}
validate()
验证服务启动需要的必要参数,然后调用doBind()
doBind()方法,他的入参是localAddress,就是我们前面new InetSocketAddress(inetPort)出来的。
private ChannelFuture doBind(final SocketAddress localAddress) {
// 1)、初始化和注册,重要***
final ChannelFuture regFuture = this.initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
} else if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
// 2)、将SocketAddress和channel绑定起来,最终执行的是nio中的功能(重要**)
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 省略异常判断、添加监听器和异步调用doBind0方法(暂且不管)
}
为方便关联对照,下面再粘贴一个简单的原生NIO编程的服务端初始化方法,其实doBind方法的逻辑基本就是对下面这个方法的封装,只是增加了很多附加功能。
这里,我去掉了细枝末节,让我们专注于核心方法,其实就两大核心一个是 initAndRegister()
,以及doBind0()。
其实,从方法名上面我们已经可以略窥一二,init->初始化,register->注册,那么到底要注册到什么呢?联系到nio里面轮询器的注册,可能是把某个东西初始化好了之后注册到selector上面去,最后bind,像是在本地绑定端口号,带着这些猜测,我们深入下去。
2.2 initAndRegister方法
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 1)、实例化channel,作为服务端初始化的是NioServerSocketChannel
channel = this.channelFactory.newChannel();
// 2)、初始化channel,即给channel中的属性赋值
this.init(channel);
} catch (Throwable var3) {
if (channel != null) {
channel.unsafe().closeForcibly();
}
return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
}
// 3)、注册,即最终是将channel 注册到selector上
ChannelFuture regFuture = this.config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
我们还是专注于核心代码,抛开边角料,我们看到 initAndRegister()
做了几件事情
- new一个channel
- init这个channel
- 将这个channel register到某个对象
2.2.1 new一个channel
从上面的代码,我们发现这条channel是通过一个 channelFactory
new出来的,channelFactory
的接口很简单:
public interface ChannelFactory<T extends Channel> extends io.netty.bootstrap.ChannelFactory<T> {
T newChannel();
}
就一个方法,我们查看channelFactory被赋值的地方:
在这里被赋值,我们层层回溯,查看该函数被调用的地方,发现最终是在这个函数中,ChannelFactory被new出:
这里,我们的demo程序调用channel(channelClass)
方法的时候,将channelClass
作为ReflectiveChannelFactory
的构造函数创建出一个ReflectiveChannelFactory:
demo端的代码如下:
.channel(NioServerSocketChannel.class);
然后回到本节最开始:
channelFactory.newChannel();
我们就可以推断出,最终是调用到 ReflectiveChannelFactory.newChannel()
方法,跟进 :
public T newChannel() {
try {
return (Channel)this.clazz.newInstance();
} catch (Throwable var2) {
throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
}
}
看出来没?是通过反射创建的。
看到clazz.newInstance();
,我们明白了,原来是通过反射的方式来创建一个对象,而这个class就是我们在ServerBootstrap
中传入的NioServerSocketChannel.class
结果,绕了一圈,最终创建channel相当于调用默认构造函数new出一个 NioServerSocketChannel
对象。
1)、new NioServerSocketChannel()
接下来我们就可以将重心放到 NioServerSocketChannel
的默认构造函数:
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static java.nio.channels.ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException var2) {
throw new ChannelException("Failed to open a server socket.", var2);
}
}
public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
super((Channel)null, channel, 16);
this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(this, this.javaChannel().socket());
}
可见,它先通过newSocket方法获取nio原生的ServerSocketChannel,然后传给了重载构造器,如下,其中第三行是对NioServerSocketChannelConfig config进行了赋值,逻辑比较简单,下面主要看对父类构造方法的调用。
2)、对NioServerSocketChannel父类构造方法的调用
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
它又调了父类,它的父类是AbstactNioChannel.class:
这里,简单地将前面 provider.openServerSocketChannel();
创建出来的 ServerSocketChannel
保存到成员变量,然后调用ch.configureBlocking(false);
设置该channel为非阻塞模式,标准的jdk nio编程的玩法。这里的 readInterestOp
即前面层层传入的 SelectionKey.OP_ACCEPT。
然后重点看对父类构造器的调用——AbstactChannel.class。
3)、AbstractChannel构造器
protected AbstractChannel(Channel parent, ChannelId id) {
this.parent = parent;
this.id = id;
this.unsafe = this.newUnsafe();
this.pipeline = this.newChannelPipeline();
}
可以看到,此构造方法只是给四个属性进行了赋值,我们挨个看下这四个属性。
第一个属性是this.parent,类型为io.netty.channel.Channel,但此时值为null;
第二个属性id——每个channel都会生成。类型为io.netty.channel.ChannelId,就是一个id生成器,值为new DefaultChannelId();
第三个属性unsafe——在每个channel之上,要去完成数据读写的对象。类型为io.netty.channel.Channel.Unsafe,该属性很重要,封装了对事件的处理逻辑,最终调用的是AbstractNioMessageChannel中的newUnsafe方法,赋的值为new NioMessageUnsafe();
第四个属性pipeline——数据处理的流程全在这里。类型为io.netty.channel.DefaultChannelPipeline,该属性很重要,封装了handler处理器的逻辑,赋的值为 new DefaultChannelPipeline(this) this即当前的NioServerSocketChannel对象。其中DefaultChannelPipeline的构造器需要额外看一下,如下,将NioServerSocketChannel对象存入channel属性,然后初始化了tail、head两个成员变量,且对应的前后指针指向对方。TailContext和HeadContext都继承了AbstractChannelHandlerContext,在这个父类里面维护了next和prev两个双向指针,看到这里有经验的园友应该一下子就能看出来,DefaultChannelPipeline内部维护了一个双向链表。
这几个个对象不是jdk nio包提供的,是netty封装的
protected DefaultChannelPipeline(Channel channel) {
this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
this.voidPromise = new VoidChannelPromise(channel, true);
this.tail = new DefaultChannelPipeline.TailContext(this);
this.head = new DefaultChannelPipeline.HeadContext(this);
this.head.next = this.tail;
this.tail.prev = this.head;
}
至此,完成了上面initAndRegister方法中的第一个功能:channel的实例化。此时NioServerSocketChannel的几个父类属性快照图如下所示:
2.2.2 init(channel)方法
init(channel)方法位于ServerBootstrap中(因为这里是通过ServerBootstrap过来的,如果是通过Bootstrap进入的这里则调用的就是Bootstrap中的init方法),主要功能如下注释所示。本质都是针对channel进行初始化,初始化channel中的option、attr和pipeline。
void init(Channel channel) throws Exception {
// 1、获取AbstractBootstrap中的options属性,与channel进行关联
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
// 2、获取AbstractBootstrap中的attr属性,与channel关联起来
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());
}
}
// 3、获取pipeline,并将一个匿名handler对象添加进去,重要***
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(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(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
初次看到这个方法,可能会觉得,哇塞,老长了,这可这么看?,庖丁解牛,逐步拆解,最后归一,下面是我的拆解步骤:
1)设置option和attr
// 1、获取AbstractBootstrap中的options属性,与channel进行关联
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
// 2、获取AbstractBootstrap中的attr属性,与channel关联起来
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());
}
}
通过这里我们可以看到,这里先调用options0()
以及attrs0()
,然后将得到的options和attrs注入到channelConfig或者channel中,关于option和attr是干嘛用的,后面再作了解。
2)设置新接入channel的option和attr
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()));
}
这里,和上面类似,只不过不是设置当前channel的这两个属性,而是对应到新进来连接对应的channel,由于我们这篇文章只关心到server如何启动,接入连接放到以后再做详细剖析。
3)加入新连接处理器
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(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(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
到了最后一步,p.addLast()
向serverChannel的流水线处理器中加入了一个 ServerBootstrapAcceptor
,从名字上就可以看出来,这是一个接入器,专门接受新请求,把新的请求扔给某个事件循环器,我们先不做过多分析
我们总结一下,我们发现其实init也没有启动服务,只是初始化了一些基本的配置和属性,以及在pipeline上加入了一个接入器,用来专门接受新连接,我们还得继续往下跟。
2.2.3 config().group().register(channel)方法
1)config().group()方法
由前面可以知道,config().group().register(channel)这行代码位于AbstractBootstrap类中的initAndRegister方法中,但由于当前对象是ServerBootstrap,故此处config()方法实际调用的都是ServerBootstrap中重写的方法,得到了ServerBootstrapConfig。
ServerBootstrapConfig的group方法如下,调用的是它的父类AbstractBootstrapConfig中的方法。通过类名就能知道,ServerBootstrapConfig中的方法是获取ServerBootstrap中的属性,而AbstractBootstrapConfig中的方法是获取AbstractBootstrap中的属性,两两对应。故此处获取的EventLoopGroup就是AbstractBootstrap中存放的group,即文章开头demo中的boss对象。
public final EventLoopGroup group() {
return bootstrap.group();
}
获取到了名叫boss的这个NioEventLoopGroup对象,下面追踪NioEventLoopGroup.register(channel)方法。
2) NioEventLoopGroup.register(channel)方法
NioEventLoopGroup继承类图:
2.1)next()方法
下面的register方法位于MultithreadEventLoopGroup类中,是NioEventLoopGroup的直接父类,如下:
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
next方法如下,调用了父类的next方法,下面的就是父类MultithreadEventExecutorGroup中的next实现,可以看到调用的是chooser的next方法。通过初始化流程可知,此处boss的线程数是1,是2的n次方,所以chooser就是PowerOfTwoEventExecutorChooser,通过next方法从EventExecutor[]中选择一个对象。需要注意的是chooser.next()通过轮询的方式选择的对象。
@Override
public EventLoop next() {
return (EventLoop) super.next();
}
@Override
public EventExecutor next() {
return chooser.next();
}
2.2)NioEventLoop.register方法
next之后是register方法,中间将NioServerSocketChannel和当前的NioEventLoop封装成一个DefaultChannelPromise对象往下传递,在下面第二个register方法中可以看到,实际上调用的是NioServerSocketChannel中的unsafe属性的register方法。
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
2.3)NioMessageUnsafe的register方法
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//非空判断
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
//判断是否注册
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
// 判断eventLoop类型是否匹配
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
//完成eventLoop属性的赋值
AbstractChannel.this.eventLoop = eventLoop;
// 判断eventLoop中的Reactor线程是不是当前线程 ***重要1
if (eventLoop.inEventLoop()) {
// 进行注册
register0(promise);
} else {
try {
// 不是当前线程则将register0任务放入eventLoop队列中让Reactor线程执行(如果Reactor线程未初始化还要将其初始化) ***重要2
eventLoop.execute(new Runnable() {
@Override
public void run() {
// 注册逻辑 ***重要3
register0(promise);
}
});
} catch (Throwable t) {
//省略异常处理。。。
}
}
}
该方法位于io.netty.channel.AbstractChannel.AbstractUnsafe中(它是NioMessageUnsafe的父类),根据注释能了解每一步做了什么,但如果要理解代码逻辑意图则需要结合netty的串行无锁化(串行无锁化参见netty系列一篇文章https://www.cnblogs.com/zzq6032010/p/12872993.html)。它实际就是让每一个NioEventLoop对象的thread属性记录一条线程,用来循环执行NioEventLoop的run方法,后续这个channel上的所有事件都由这一条线程来执行,如果当前线程不是Reactor线程,则会将任务放入队列中,Reactor线程会不断从队列中获取任务执行。这样以来,所有事件都由一条线程顺序处理,线程安全,也就不需要加锁了。
说完整体思路,再来结合代码看看。上述代码中标识【***重要1】的地方就是通过inEventLoop方法判断eventLoop中的thread属性记录的线程是不是当前线程:
先调到父类AbstractEventExecutor中,获取了当前线程:
public boolean inEventLoop() {
return this.inEventLoop(Thread.currentThread());
}
然后调到SingleThreadEventExecutor类中的方法,如下,比对thread与当前线程是否是同一个:
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
此时thread未初始化,所以肯定返回false,则进入【***重点2】的逻辑,将register放入run方法中封装成一个Runnable任务,然后执行execute方法,如下,该方法位于SingleThreadEventExecutor中:
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} else {
boolean inEventLoop = this.inEventLoop();
if (inEventLoop) {
//将任务放入队列中 ***重要a
this.addTask(task);
} else {
//判断当前线程不是thread线程,则调用该方法 ***重要b
this.startThread();
this.addTask(task);
if (this.isShutdown() && this.removeTask(task)) {
reject();
}
}
if (!this.addTaskWakesUp && this.wakesUpForTask(task)) {
this.wakeup(inEventLoop);
}
}
}
有两个重要的逻辑,已经在上面代码中标出,先看看【***重要a】,如下,可见最终就是往SingleThreadEventExecutor的taskQueue队列中添加了一个任务,如果添加失败则调reject方法执行拒绝策略,通过前文分析可以知道,此处的拒绝策略就是直接抛错。
protected void addTask(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} else {
if (!this.offerTask(task)) {
this.reject(task);
}
}
}
final boolean offerTask(Runnable task) {
if (this.isShutdown()) {
reject();
}
return this.taskQueue.offer(task);
}
然后在看【***重要b】,如下,该方法虽然叫startThread,但内部有控制,不能无脑开启线程,因为调这个方法的时候会有两种情况:1).thread变量为空;2).thread不为空且不是当前线程。第一种情况需要开启新的线程,但第二种情况就不能直接创建线程了。所以看下面代码可以发现,它内部通过CAS+volatile(state属性加了volatile修饰)实现的开启线程的原子控制,保证多线程情况下也只会有一个线程进入doStartThread()方法。
private void startThread() {
if (STATE_UPDATER.get(this) == 1 && STATE_UPDATER.compareAndSet(this, 1, 2)) {
this.doStartThread();
}
}
继续往下看一下doStartThread()的方法逻辑:
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() { //此处的executor内部执行的就是ThreadPerTaskExecutor的execute逻辑,创建一个新线程运行下面的run方法
@Override
public void run() {
thread = Thread.currentThread(); //将Reactor线程记录到thread变量中,保证一个NioEventLoop只有一个主线程在运行
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run(); //调用当前对象的run方法,该run方法就是Reactor线程的核心逻辑方法,后面会重点研究
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// 省略无关逻辑
}
}
});
}
可以看到,在上面的方法中完成了Reactor线程thread的赋值和核心逻辑NioEventLoop中run方法的启动。这个run方法启动后,第一步做的事情是什么?让我们往前回溯,回到3.2.3),当然是执行当初封装了 register0方法的那个run方法的任务,即执行register0方法,下面填之前埋得坑,对【***重要3】进行追踪:
private void register0(ChannelPromise promise) {
try {
// 省略判断逻辑
boolean firstRegistration = neverRegistered;
doRegister();// 执行注册逻辑
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();// 调用pipeline的逻辑
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// 省略无关逻辑
} catch (Throwable t) {
// 省略异常处理
}
}
doRegister()方法的实现在AbstractNioChannel中,如下,就是完成了nio中的注册,将nio的ServerSocketChannel注册到selector上:
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
// 省略异常处理
}
}
}
再看pipeline.invokeHandlerAddedIfNeeded()方法,该方法调用链路比较长,此处就不详细粘贴了,只是说一下流程。回顾下上面第二部分的第2步,在里面最后addLast了一个匿名的内部对象,重写了initChannel方法,此处通过pipeline.invokeHandlerAddedIfNeeded()方法就会调用到这个匿名对象的initChannel方法(只有第一次注册时才会调),该方法往pipeline中又添加了一个ServerBootstrapAcceptor对象。执行完方法后,netty会在finally中将之前那个匿名内部对象给remove掉,这时pipeline中的handler如下所示:
至此,算是基本完成了initAndRegister方法的逻辑,当然限于篇幅(本篇已经够长了),其中还有很多细节性的处理未提及。
三、AbstractBootstrap的doBind0方法
我们发现,在调用doBind0(...)
方法的时候,是通过包装一个Runnable进行异步化的,关于异步化task,可以看下这篇文章,netty源码分析之揭开reactor线程的面纱(三)
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());
}
}
});
}
好,接下来我们进入到channel.bind()
方法:AbstractChannel.java
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
发现是调用pipeline的bind方法:
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
相信你对tail是什么不是很了解,我也不了解,可以翻到最开始,tail在创建pipeline的时候出现过,关于pipeline和tail对应的类,我后面源码系列会详细解说,这里,你要想知道接下来代码的走向,唯一一个比较好的方式就是debug 单步进入,篇幅原因,我就不详细展开。
tail.bind方法:
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (!validatePromise(promise, false)) {
// cancelled
return promise;
}
// 从tail开始往前,找到第一个outbond的handler,这时只有head满足要求,故此处next是head
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
// 因为当前线程就是executor中的Reactor线程,所以直接进入invokeBind方法
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
下面进入head.invokeBind方法:
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
核心逻辑就是handler.bind方法,继续追踪:
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
这里的unsafe就是前面提到的 AbstractUnsafe
, 准确点,应该是 NioMessageUnsafe
;我们进入到它的bind方法:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// ...
boolean wasActive = isActive();
// ...
doBind(localAddress);
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
继续追踪会看到在bind方法中又调用了NioServerSocketChannel中的doBind方法,最终在这里完成了nio原生ServerSocketChannel和address的绑定:
NioServerSocketChannel.class:
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
至此,ServerBootstrap的bind方法完成。
四、总结
最后,我们来做下总结,netty启动一个服务所经过的流程
- 设置启动类参数,最重要的就是设置channel
- 创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等
- 初始化server对应的channel,设置一些attr,option,以及设置子channel的attr,option,给server的channel添加新channel接入器,并出发addHandler,register等事件
- 调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定
参考文章:https://zhuanlan.zhihu.com/p/37512332
https://www.cnblogs.com/zzq6032010/p/13034608.html
https://blog.csdn.net/weixin_33904756/article/details/85976683