Connector:
在上一章中,分析了整个Tomcat
的结构以及启动流程,在整个框架中,我们分析了StandardServer
,StandardService
等的初始化以及启动,但是对于连接器,却是一笔带过?为什么呢?并不是因为连接器不重要,而是它太重要了,所以这里我们单独说一说它。
首先,在看Tomcat
源码的时候,按照Tomcat
的命名习惯,我一直在找StandardConnector
,后来才发现它的类名就叫Connector
,因为Connector
并不是接口,而就是一个简单的类。
其次,需要明白的是,Tomcat
连接器的作用是监听端口,包装Request
和Response
,而在Tomcat
的实现中,分别支持两种协议:Http
和Ajp
,
Http
就是常规的超文本传输协议,用来做Web
服务器,Ajp
是用来与其他服务器通信用的,比如Apache
静态服务器,常规下,Tomcat
在传输静态资源,比如image,html
等比起Apache
来说,性能一般,如果一个服务中静态资源比较多的话,可以使用Apache
做负载均衡加静态资源服务器,当一个请求到来的时候,Apache
会首先拦截到请求,如果是静态资源请求,则Apache
会直接处理然后返回,如果是动态资源请求,则Apache
会通过Ajp
协议发送给Tomcat
,从而将请求转发给Tomcat
因此,平时我们如果不使用Apache
做前端服务器,是不需要开启Ajp
协议的。
最后,需要明白的是Tomcat
的连接器监听IO
的方式,分别包括BIO
,NIO
和APR
方法,BIO
便是传统阻塞式IO,每个线程独占一个IO
,NIO
可以翻译为New IO
,也可以叫非阻塞式IO,它是一种同步非阻塞式IO,相对BIO
,性能大大的提高,在Tomcat 9中,好像已经没有了BIO
实现的选项,APR
是一种通过C
语言在操作系统层面实现的IO
,性能更加高,不过需要额外的配置.
明白了上面的前提以后,咱们再来看Connector
的代码就比较容易明白了。
protected final ProtocolHandler protocolHandler;
protected Adapter adapter = null;
以上两个是Connector
最主要属性,其中ProtocolHandler
是连接器中主要干活的,Adapter
是用来适配生成Request
和Response
的。
首先看连接器的构造方法代码:
public Connector(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
}
} else {
protocolHandlerClassName = protocol;
}
// Instantiate protocol handler
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
可以看到,Connector
会根据server.xml
的配置初始化ProtocolHandler
,分别有以下几种:
org.apache.coyote.http11.Http11AprProtocol
:支持HTTP
协议的Apr
IO处理org.apache.coyote.http11.Http11NioProtocol
:支持HTTP
协议的NIO
IO处理org.apache.coyote.ajp.AjpAprProtocol
:支持Ajp
协议的Apr
IO处理org.apache.coyote.ajp.AjpNioProtocol
:支持Ajp
协议的NIO
IO处理
以及其他协议,可是在org.apache.coyote
包中已经完全没有了BIO
的影子,可以发现Tocmat 9
已经完全完全放弃了BIO
。
官方中表明
8.0
开始默认使用NIO
,8.5
已经废弃了BIO
连接器初始化完成后,会根据指定的类的全路径的初始化对象的ProtocolHandler
,由于默认是NIO
,因此我们这里以org.apache.coyote.http11.Http11NioProtocol
为参考查看源代码。
构造完成后,连接器应该被inti
,这里看下连接器的initInternal
的代码:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (protocolHandler == null) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));
}
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
if (service != null) {
protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
}
// 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());
}
}
protocolHandler.init();
}
删除了一些结构性的代码。
- 首先,调用父类的
initInternal
,像JMX
注册自己 - 然后,初始化适配器,并将适配器赋值给
protocolHandler
- 初始化后台任务线程池
- 判断是否启动
Apr
,若需要启用,则再初始化Apr
- 调用
protocoHandler
的初始化方法
这里最关键的便是 调用
protocoHandler
的初始化方法
连接器的startInternal()
方法比较简单,就是单纯的启动protocolHandler
这里可以看出来,连接器中真正干活的其实是ProtocolHandler
接下来看看org.apache.coyote.http11.Http11NioProtocol
的start()
方法和init()
方法
查看源代码我们可以发现,还存在一个
Http11Nio2Protocol
这个又是什么呢?前面说过,一般
NIO
是同步非阻塞IO
,因此Http11NioProtocol
是用来实现同步非阻塞IO
,而对于Http11Nio2Protocol
则是用来实现异步非阻塞IO
。
Http11NioProtocol
的主要代码有很多在其父类:由上向下查看代码:
AbstractProtocol # init
@Override
public void init() throws Exception {
...
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
继续往下看,我们能看到endPoint.init()
默认使用了AbstractEndPoint
的init()
方法:
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
}
删除了部分结构性代码
private void bindWithCleanup() throws Exception {
try {
bind();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
unbind();
throw t;
}
}
可以发现,最终EndPoint
的init()
是调用了bind()
方法,
这个方法比较重要
@Override
public void bind() throws Exception {
initServerSocket();
//设置acceptor线程数量
if (acceptorThreadCount == 0) {
//这里有条作者的注释:多线处理acceptor性能不见得好
acceptorThreadCount = 1;
}
//设置poller的线程数量
if (pollerThreadCount <= 0) {
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
//初始化ssl
initialiseSsl();
//打开选择器连接池
selectorPool.open();
}
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
//开启socket管道
serverSock = ServerSocketChannel.open();
//设置socket属性
socketProperties.setProperties(serverSock.socket());
//根据配置设置端口等信息
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
//绑定socket监听地址,以及设置最大连接数
serverSock.socket().bind(addr,getAcceptCount());
} else {
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
//设置IO为阻塞IO
serverSock.configureBlocking(true); //mimic APR behavior
}
- 可以看到,
bind
中调用了initServerSocket()
方法,initServerSocket()
方法初始化NIO
中的管道以及socket
接下来看start()
方法:
@Override
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());
//创建工作线程池
if ( getExecutor() == null ) {
createExecutor();
}
//设置连接最大数,默认1000
initializeConnectionLatch();
// 开启pooler线程
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();
}
}
- 可以看到,
start()
方法主要就是设置对象缓存池和开启线程 - 到现在我们还没找到监听到请求后是如何处理的
Accptor
和Pooler
是干什么的?
带着上面的问题,我们继续看Acceptor
的代码:
Acceptor
#run()
@Override
public void run() {
int errorDelay = 0;
// 循环处理endpoint
while (endpoint.isRunning()) {
// 如果 endpoint 处于 pause 状态,这边 Acceptor 用一个 while 循环将自己也挂起
while (endpoint.isPaused() && endpoint.isRunning()) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
// 判断endpoint状态
if (!endpoint.isRunning()) {
break;
}
state = AcceptorState.RUNNING;
// 如果此时达到了最大连接数(之前我们说过,默认是10000),就等待
endpoint.countUpOrAwaitConnection();
if (endpoint.isPaused()) {
continue;
}
U socket = null;
try {
// 这里就是接收下一个进来的 SocketChannel
// 之前我们设置了 ServerSocketChannel 为阻塞模式,所以这边的 accept 是阻塞的
socket = endpoint.serverSocketAccept();
} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
if (endpoint.isRunning()) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// accept 成功,将 errorDelay 设置为 0
errorDelay = 0;
if (endpoint.isRunning() && !endpoint.isPaused()) {
// setSocketOptions() 是这里的关键方法,也就是说前面千辛万苦都是为了能到这里进行处理
if (!endpoint.setSocketOptions(socket)) {
// 如果上面的方法返回 false,关闭 SocketChannel
endpoint.closeSocket(socket);
}
} else {
// 由于 endpoint 不 running 了,或者处于 pause 了,将此 SocketChannel 关闭
endpoint.destroySocket(socket);
}
}
state = AcceptorState.ENDED;
}
删除了部分结构性的代码
其实,上面的代码是NIO
带来的复杂度,其实核心代码就两句:
socket = endpoint.serverSocketAccept();
endpoint.setSocketOptions(socket)
所以上面的代码可以暂时不管,就研究这两句
所以手写
NIO
还是比较麻烦
EndPoint#serverSocketAccept()
@Override
protected SocketChannel serverSocketAccept() throws Exception {
return serverSock.accept();
}
可以看到,这就是一个阻塞方法,用来获取serverSocket
监听方法,当获取到socket
后,就将socket
发送给endpoint
的setSocketOptions()
中
@Override
protected boolean setSocketOptions(SocketChannel socket) {
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
NioChannel channel = nioChannels.pop();
if (channel == null) {
SocketBufferHandler bufhandler = new SocketBufferHandler(
socketProperties.getAppReadBufSize(),
socketProperties.getAppWriteBufSize(),
socketProperties.getDirectBuffer());
if (isSSLEnabled()) {
channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);
} else {
channel = new NioChannel(socket, bufhandler);
}
} else {
channel.setIOChannel(socket);
channel.reset();
}
getPoller0().register(channel);
return true
}
删除了一些结构性的代码
可以看到,关键就是getPoller0().register(channel);
获取到socket
后就将socket
交给Poller
处理
到这里,我们基本能明白:
Acceptor
用来循环监听socket
Poller
用来循环处理socket
Poller
的处理socket
比较复杂,关系到NIO
的具体编程,这里暂时不再分析。
不过在其run
方法里,包含一个processSocket()
方法,这个方法比较重要,processSocket()
会调用createSocketProcessor()
创建一个SocketProcessor()
对象,这个对象实现了Runable
接口,用来包装生成Request
和Response
对象以及调用容器(Container)的Pipeline#invoke()方法“
Pipeline pipeline = connector.getService().getContainer().getPipeline();
在这里,就将连接器和容器串联起来了。
总结
看了上面这么多方法,其实总结起来如下:
- 首先连接器(
Connector
)包含一个Protocol
对象(协议对象,不同的协议实现不同),连接器负责组合适配器和根据配置生成Protocol
对象 Protocol
对象负责一些配置文件的读取,根据协议封装方法等,以及包含一个EndPoint
对象EndPoint
对象包含一个Acceptor
和Poller
对象EndPoint
的start()
方法会分别启动Acceptor
和Poller
- 当一个连接到来的时候,
Acceptor
负责接收这个连接,然后将连接传给Poller
处理,Poller
会调用适配器封装Request
和Response
最后调用容器的请求处理方法