一. 前言
前面分析了tomcat的整体架构和tomcat的启动过程,在分析启动过程的时候只讲了整体的启动过程,本篇来重点分析一下tomcat的Connector(连接器)组件的启动过程。
二.从Connector的构造开始
那么org.apache.catalina.connector.Connector
是在什么时候被创建出来的呢?在上一篇tomcat的启动过程中我们跟进过了Digester
解析tomcat的server.xml
配置文件,在org.apache.catalina.startup.Catalina#createStartDigester
方法中有一个段代码是:
// 此处使用的tomcat版本为8.0
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
所以当解析到server.xml
配置文件中的Connector
标签之后,就会创建一个ConnectorCreateRule
来执行Connector
的相关操作。那么这个org.apache.catalina.startup.ConnectorCreateRule
其实是一个org.apache.tomcat.util.digester.Rule
,这个Rule
接口主要是实现了在匹配相应的XML元素嵌套模式时要采取的操作,简单说就是他是规定XML解析的元素是如何被加载处理的,Rule
有一个方法org.apache.tomcat.util.digester.Rule#begin
,就是用来处理XML配置文件中的标签开头部分的,也就初始化之类的。
默认是空实现,所以我们来看org.apache.catalina.startup.ConnectorCreateRule
的start
方法。
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Service svc = (Service)digester.peek();
Executor ex = null;
if ( attributes.getValue("executor")!=null ) {
ex = svc.getExecutor(attributes.getValue("executor"));
}
// 从配置文件中的Connector标签中获取protocol属性的值,那么默认我们的protocol值就是HTTP/1.1
Connector con = new Connector(attributes.getValue("protocol"));
if (ex != null) {
setExecutor(con, ex);
}
String sslImplementationName = attributes.getValue("sslImplementationName");
if (sslImplementationName != null) {
setSSLImplementationName(con, sslImplementationName);
}
digester.push(con);
}
由上面这段代码注释可以知道是通过xml的配置内容进行Connector
的创建的,那么跟进到org.apache.catalina.connector.Connector#Connector(java.lang.String)
public Connector(String protocol) {
// 从上面的跟进可以知道入参的protocol值为 HTTP/1.1
// 这个setProtocol就是根据入参去选择对应的Protocol
// 那么由此可知最终是protocolHandlerClassName的值为org.apache.coyote.http11.Http11NioProtocol
// 也就是http1.1的nio方式
setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
// 此处通过反射创建Http11NioProtocol
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
// Default for Connector depends on this (deprecated) system property
if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
encodedSolidusHandling = EncodedSolidusHandling.DECODE;
}
}
通过这段代码我们跟进到org.apache.coyote.http11.Http11NioProtocol
的创建,来看Http11NioProtocol
的继承结构
public Http11NioProtocol() {
//创建了一个NioEndpoint
super(new NioEndpoint());
}
Http11NioProtocol
在构造时创建了一个org.apache.tomcat.util.net.NioEndpoint
,这个其实就是NIO的默认实现。Endpoint
负责网络通信,监听一个端口,循环接收socket请求,读取网络字节流等
在NioEndpoint
的父类org.apache.tomcat.util.net.AbstractEndpoint
中有一个抽象内部类:org.apache.tomcat.util.net.AbstractEndpoint.Acceptor
,而NioEndpoint
中有一个org.apache.tomcat.util.net.NioEndpoint.Acceptor
继承自它,并实现了他的run()
方法,在这个run方法中就是在循环的调用serverSock.accept()
接收请求到tomcat的链接,先有个大概了解,后续在详细分析接收请求的部分。
回到Http11NioProtocol
的创建
public Http11NioProtocol() {
//创建了一个NioEndpoint
super(new NioEndpoint());
}
跟进到父类的构造函数可以知道,最终是把这个new出来的 NioEndpoint
赋值给了org.apache.coyote.AbstractProtocol#AbstractProtocol
的org.apache.coyote.AbstractProtocol#endpoint
属性。在父类org.apache.coyote.http11.AbstractHttp11Protocol
构造器中:
public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
创建了一个org.apache.coyote.AbstractProtocol.ConnectionHandler
,然后把这个ConnectionHandler
设置到前面创建的NioEndpoint
中。
然后就完成了Connector
的创建。在Connector
的创建过程中,我们要注意几个点:
Http11NioProtocol
继承自AbstractProtocol
和AbstractHttp11Protocol
,他持有NioEndpoint
NioEndpoint
继承自AbstractEndpoint
,持有ConnectionHandler
,NioEndpoint
的内部类Acceptor
是真正的接收请求的地方ConnectionHandler
是NioEndpoint
的一个属性,它在后续会进行NioEndpoint
接收的请求处理时使用
到目前为止只是创建了Connector
,Connector
也是一个Lifecycle
,所以接下来就到了他的initInternal
,所以我们直接来到:org.apache.catalina.connector.Connector#initInternal
,不了解为啥会来到这里的同学请先看tomcat的启动过程中对Lifecycle
相关的内容。
三. Connector的init
Connector
实现了Lifecycle
,所以init肯定是在org.apache.catalina.connector.Connector#initInternal
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
// 初始化一个CoyoteAdapter CoyoteAdapter是Adapter接口的实现
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
getProtocolHandlerClassName()));
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
// protocolHandler就是Http11NioProtocol
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
跟进到protocolHandler.init();
执行里面去,由于Http11NioProtocol
集成了AbstractHttp11Protocol
,这个init()
方法就在AbstractHttp11Protocol
中:
public void init() throws Exception {
// Upgrade protocols have to be configured first since the endpoint
// init (triggered via super.init() below) uses this list to configure
// the list of ALPN protocols to advertise
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
configureUpgradeProtocol(upgradeProtocol);
}
// 又调用了父类的init,也就是AbstractProtocol的init方法
super.init();
// Set the Http11Protocol (i.e. this) for any upgrade protocols once
// this has completed initialisation as the upgrade protocols may expect this
// to be initialised when the call is made
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
if (upgradeProtocol instanceof Http2Protocol) {
((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
}
}
}
跟进到org.apache.coyote.AbstractProtocol#init
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
this.rgOname = rgOname;
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
// 这里的endpoint就是NioEndpoint 此处给NioEndpoint设置了名字:http-nio-8080 和 domain:Catalina
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// 然后继续执行自组件的init 也就是NioEndpoint的init
endpoint.init();
}
跟进这个endpoint.init();
最终会来到:org.apache.tomcat.util.net.AbstractEndpoint#init
,这个方法中我们重点关注其中的bind();
方法,其他的代码这里就不贴出来了。
bind()
方法是个抽象方法,留给子类实现了,所以来到:org.apache.tomcat.util.net.NioEndpoint#bind
public void bind() throws Exception {
/*默认执行*/
if (!getUseInheritedChannel()) {
// 开启 ServerSocketChannel
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
// addr是:0.0.0.0/0.0.0.0:8080 其实就是绑定8080端口
// getAcceptCount()获取的是acceptCount 默认值为100 表示的是socket上请求的最大挂起连接数
serverSock.socket().bind(addr,getAcceptCount());
} else {
/*从系统获取*/
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
// 设置 ServerSocketChannel 为阻塞模式
serverSock.configureBlocking(true); //mimic APR behavior
// Initialize thread count defaults for acceptor, poller
// 设置了Poller和Acceptor的线程数量 在start中根据数量启动相对应数量的线程
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
// 设置
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
initialiseSsl();
// 打开NioSelectorPool
selectorPool.open();
}
然后Connector
的init大致就完成了。
在这个过程中主要是有:
- 创建了
CoyoteAdapter
- 执行
Http11NioProtocol
的init,在执行endpoint的init
- 在
NioEndpoint
中开启了ServerSocketChannel
并绑定端口 - 打开了一个
NioSelectorPool
四. Connector的start
大致的了解了的一遍Connector
的init
之后,再来看Connector
的start
,从org.apache.catalina.connector.Connector#startInternal
开始
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
// Http11NioProtocol的start
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
根据代码跟进到:org.apache.coyote.AbstractProtocol#start
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
// NioEndpoint的start
endpoint.start();
// Start timeout thread
// 启动超时线程
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
跟进endpoint.start();
会先来到org.apache.tomcat.util.net.AbstractEndpoint#start
public final void start() throws Exception {
// 这个bindState在org.apache.tomcat.util.net.AbstractEndpoint#init中已经设置为BOUND_ON_INIT了 所以这里不会再进入这个if
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
// 跟进这个
startInternal();
}
跟进startInternal()
到org.apache.tomcat.util.net.NioEndpoint#startInternal
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
/*创建工作线程池*/
if (getExecutor() == null) {
// tomcat自定义了线程池实现,并自定义了线程池队列 这个后续再分析
createExecutor();
}
//限流控制 tomcat自定义了一个LimitLatch用来实现限流控制
initializeConnectionLatch();
// Start poller threads
// 启动Poller线程
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 启动Acceptor线程
startAcceptorThreads();
}
}
在org.apache.tomcat.util.net.NioEndpoint#bind
中设置了Poller
和Acceptor
的数量
那么至此start也就大致完成了,在这个过程中几个重点:
- 创建了工作线程池
org.apache.tomcat.util.threads.ThreadPoolExecutor
- 创建了限流
LimitLatch
- 启动了
Poller
线程 - 启动了
Acceptor
线程
到此位置,Connector的启动过程就大致分析完成了。在过程中涉及到了Http11NioProtocol
、NioEndpoint
、ConnectionHandler
、CoyoteAdapter
、Acceptor
、Poller
、LimitLatch
、org.apache.tomcat.util.threads.ThreadPoolExecutor
这些重要的类。这些都在后续的文章中详细分析吧,由于篇幅问题,本篇就先了解一个过程,算是抛砖引玉