Tomcat8.5源码学习二之Connector
Healthy Mind Lv3

​ 上一篇看到 了 Connector的初始化 这一篇学习一下Connector 的源码 还是选看tomcat的架构图

​ Connector 是tomcat 和外界交互的一个组件,对我们优化tomcat 的吞吐量和并发数,很有帮助,对于我们在写类似该功能的时候提供了一些参考借鉴的思路。

org.apache.catalina.util.LifecycleBase initial() 方法这里使用的 设计模是模板方法 还是老样子 查看 Connector 的 initInternal() 方法

关于tomcat 各个组件的初始化请查看 Tomcat内嵌和服务启动的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override
protected void initInternal() throws LifecycleException {

super.initInternal();

// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);

// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}

if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
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.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}

这里可以看到最主要的是 protocolHandler 这个变量,这个的初始化,在这个代码里是看不到 的,这个是由Server.xml配置来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->

<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
type="RSA" />
</SSLHostConfig>
</Connector>
-->

在 Connecotr 的protocol 属性可以看出可以是 org.apache.coyote.http11.Http11NioProtocol 也可以是 HTTP/1.1 协议版本,以下是 org.apache.coyote.ProtocolHandler中各个协议版本的 实现

但是在 protocolHandler.init(); 上我们使用 Ctrl+Alt+B 只有两个实现,现在大部分使用还是 HTTP/1.1版本 我们可以查看这个的具体实现 其中

NIO 传统的BIO方式是基于流行进行读写的,而且是阻塞的,整体性能比较差。为了提高I/O性能,JDK与1.4版本引入NIo,他弥补了原来BIO方式的不足,在标准的Java代码中提供了高速、面向块的I/O。通过定义包含数据的类以及块的形式处理数据,这是BIO无法做到的。

​ NIO2是JDK7新增的文件及网络I/O特性,他继承自NIO,同时添加了众多特性及功能改进,其中最重要的即是对异步I/O(AIO)的支持。

所有的 ProtocolHandler 的具体实现类都继承 org.apache.coyote.AbstractProtocol 这个类 在 protocolHandler.init(); 要选看看 org.apache.coyote.AbstractProtocol 的 init()方法到底做了啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
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) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}

String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);

endpoint.init();
}

这里最主要的是 就是 endpoint.init() 这个代码了,看看里面写了啥 这里有两个实现

1
2
3
public abstract class AbstractJsseEndpoint<S> extends AbstractEndpoint<S> {
.....
}

AbstractJsseEndpoint 还是继承 AbstractEndpoint 我们看看 AbstractEndpoint 个类的 init() 方法就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void init() throws Exception {
if (bindOnInit) {
//绑定 啥不清楚
bind();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);

ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
socketProperties.setObjectName(socketPropertiesOname);
Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);

for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}

上面 有一个 bind() 其他就是注册的一些方法,我们看 bind();

发现 bind() 是一个 abstract 修饰的方法,这里其实也是模板方法看看有哪些类实现了 bind()

这里我们可以看看 org.apache.tomcat.util.net.NioEndpointorg.apache.tomcat.util.net.Nio2Endpoint 看看 tomcat是怎么使用 这两种不同 io的 以及这两个io的区别

org.apache.tomcat.util.net.NioEndpointbind() 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Initialize the endpoint.
*/
@Override
public void bind() throws Exception {

if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
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"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior

// Initialize thread count defaults for acceptor, poller
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();

selectorPool.open();
}

bind() 看到其实就是端口绑定。

这里是 nio 的selector acceptorThreadCount 和 pollerThreadCount 从命名可以看出是 接受连接的线程数 和轮询 连接的线程数,最后是 selectorPool.open(); 点进去 可以看到 我们熟悉的 nio java.nio.channels.Selector

到此,初始化 Connector 就算完成了,接下来就是 处理请求,如果一个连接来了以后 是怎么处理的。逻辑是如何。