tomcat+spring mvc 原理(三):tomcat网络请求的监控与处理1
- 前言:
- 请求处理的准备
- Connector处理请求
- Connector内部结构
- 请求处理的动态实现
前言:
tomcat + spring mvc 原理(一):tomcat原理综述总结了tomcat的整体运作原理、静态容器架构和容器配置,tomcat + spring mvc 原理(二):tomcat容器的初始加载与启动详细介绍了tomcat动态容器初始化和启动过程。本文主要介绍tomcat网络请求的监控与处理逻辑。
利用tomcat + spring mvc架构体系构建服务器,很重要的一个部分就是网络请求的监控、接收、解包、封包、请求传递等功能的实现,这样在建服务器时就只需要关注业务参数的传入和处理,而不用关心网络协议等和业务无关的细节。 一般服务的问题都出现在请求和应答处理的过程,一旦是因为框架内部导致的问题,这时候充分了解tomcat+spring mvc的请求处理逻辑就很重要了。
请求处理的准备
网络请求相关的处理集中在Connector容器中。由原理一中介绍的总体容器架构可知,Connector是被包含在Service容器中,而且Connector容器的配置也是包含在Service的配置中:
<Service name="Catalina">
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector 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>
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
certificateFile="conf/localhost-rsa-cert.pem"
certificateChainFile="conf/localhost-rsa-chain.pem"
type="RSA" />
</SSLHostConfig>
</Connector>
......
</Service>
以上是我从原理(一)介绍的tomcat的默认配置文件server.xml中捞出来的配置。可以看到,<Connector>标签是包含在标签中的<Service>中的,一个Service可以有多个Connector,可以配置线程池来处理请求。tomcat在启动过程中依据这个配置创建Service和Connector,然后根据原理(二)中介绍的流程初始化和启动Service和Connector。
Connector处理请求
Connector内部结构
一个Connector中会有一个ProtocolHandler,不同的ProtocolHandler接口的实现代表不同的协议,可以看到上文的配置中包括了协议的类的设置,比如protocol="HTTP/1.1"或者protocol=“org.apache.coyote.http11.Http11NioProtocol”。默认实现的协议主要有两种,Ajp或者HTTP。HTTP协议不用说,Ajp是Apache JServ Protocol的缩写,主要用于Apache前端服务器的通讯,特点是长连接,不需要每次断开连接再建立连接,开销比较小(偷偷查了下资料)。
ProtocolHandler中会包含Endpoint、Processor和Adapter的实现。Endpoint主要用来管理Socket,监听TCP的请求和发送应答。Processor用来实现HTTP协议。Adapter,顾名思义,是作为适配器将请求适配到Container容器。
请求处理的动态实现
Connector的构造函数中,会根据配置选择ProtocolHandler的实现:
public Connector(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
/**
*下面blabla一堆是用来判断使用哪一个ProtocolHandler的实现
**/
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的生命周期方法initInternal()和startInternal()中(原理(二)有讲到)都调用了ProtocolHandler相应的init()和start()生命周期方法。需要注意的是,在initInternal()中调用protocolHandler.init()之前,先设置了protocolHanler的Adpter。
@Override
protected void initInternal() throws LifecycleException {
······
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
······
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
同样的故事也发生在Endpoint身上。Endpoint构造发生在ProtocolHandler的实现类构造函数中:
public Http11NioProtocol() {
super(new NioEndpoint());
}
在ProtocolHandler的init()和start()生命周期方法中调用了Endpoint的相应生命周期的init()和start()方法(由此可见生命周期这一抽象的应用在tomcat中随处可见)。
Endpoint工作在连接和传输数据的最前线。父类AbstractEndpoint的init()方法中调用了bind()方法,start()方法调用了Endpoint的startInternal()方法。
//init()方法
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
......
}
//bindWithCleanup()调用bind
private void bindWithCleanup() throws Exception {
try {
bind();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
unbind();
throw t;
}
}
//start()中调用startInternal()同原理(二)中所述,不再列出
其中bind()方法和startInternal()方法都是在子类NioEndpoint中实现。
NioEndpoint使用的套接字是JDK1.4之后新增的NioSocket,相对于普通Socket效率要更高。NioSocket中的实现关键是Selector和Channel,Channel需要注册到关注的Selector(具体可以参看NioSocket的相关讲解)。在bind()方法中依次创建 ServerSocketChannel和Selector的线程池。需要注意的线程池中的基本对象是Poller,Poller继承自Thread,内部包含了一个Selector,实现了Selector的select()的持续监听。
public void bind() throws Exception {
//见下。创建Channel
initServerSocket();
setStopLatch(new CountDownLatch(1));
initialiseSsl();
//Selector线程池
selectorPool.open(getName());
}
//initServerSocket()
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
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
}
startInternal()中启动了Acceptor,Acceptor主要的作用是调用Endpoint中方法实现Poller和Channel的注册和请求连接的监听,然后获取请求的SocketChannel。
public void startInternal() throws Exception {
······
startAcceptorThread();
······
}
}
标准Poller的实现是继承自Thread,其中的run()方法主要是Selector.select(),等待请求的到来,然后循环遍历SelectKey
(这部分中的实现和NioSocket的使用强相关,没有了解过一定要提前看下),在循环中调用processKey()方法。
public class Poller implements Runnable {
@Override
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
/**
*Selector.select()监听到来的请求
**/
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
......
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
if (socketWrapper == null) {
iterator.remove();
} else {
iterator.remove();
////
processKey(sk, socketWrapper);
////
}
}
timeout(keyCount,hasEvents);
}
getStopLatch().countDown();
}
}
SocketWrapper可以说是一个大杂烩,里面包括了各种和连接有关的东西,比如Endpoint、Selector的pool、 NioChannel的栈、Poller、interestOps(与SelectorKey相关的操作码)、SendfileDataSocketBufferHandler、IOException等等。
processKey()调用了SocketWrraper对象中Channel的读操作,将消息读入到Buffer中,然后调用processSocket()方法,进一步处理消息.processSocket()中获取了一个SocketProcessor类对象(注意并不是图上的Processor),并调用了它的run()方法。这个类的父类SocketProcessorBase实现了Runnable,在run()中调用了doRun()方法,SocketProcessor类中实现了doRun()方法。SocketProcessor是Endpoint的内部类,它的doRun()方法调用Endpoint的Handler的process方法(终于绕回到我的图了)。
Handler的process()方法主要就是处理和Processor选用相关的逻辑,Processor用来对TCP的消息进行解包,因此和ProtocolHandler一样,有不同协议的实现。其中,Http11Processor的实现就主要对Http的request和response进行解包和封包。
Handler的process获取了Processor对象,然后调用了AbstractProcessorLight(是实现了Processor的抽象类)的process()方法,process()中调用了两个比较重要的方法:service()和dispatch()。service()对于不同协议有不同的实现,Http11Processor的service实现就是对接收的request进行HTTP解包,如果有错误会对response进行设置。dispatch()方法中调用:
getAdapter().asyncDispatch(request, response, status)
利用Adapter的asyncDispatch()方法先将org.apache.coyote.Request request和org.apache.coyote.Response response分别转换为org.apache.catalina.connector.Request和org.apache.catalina.connector.Response,然后利用自身包含的Connector对象(还记得上文说的“需要注意的是,在initInternal()中调用protocolHandler.init()之前,先设置了protocolHanler的Adpter”吗?Connector也在这个时候将自己的实例设置到了Adapter唯一实现CoyoteAdapter实例中):
connector.getService().getContainer().getPipeline()
.getFirst().invoke(request, response);
将request和response传递给了Container(原理一和原理二中提到了Engine、Host、Context和Wrapper等)。
由于本文章的内容已经过多,特别是在在Endpoint部分中纠缠往复,很难理清。关于后续Container中请求的处理和调用Filter对请求拦截的内容就放在下期来讲。
相关文章:
tomcat + spring mvc原理(一):tomcat原理综述和静态架构tomcat + spring mvc原理(二):tomcat容器初始化加载和启动tomcat + spring mvc原理(四):tomcat网络请求的监控与处理2tomcat + spring mvc原理(五):tomcat Filter组件实现原理