先了解下 Tomcat 结构
研读源码之前,让我们先了解一下 ta 的架构组成,使用过tomcat的朋友,应该知道tomcat的conf/server.xml
文件。
<!-- server是tomcat的顶级容器,表示整个tomcat服务。它包含至少一个Service,用于提供具体的服务 -->
<Server port="8005" shutdown="SHUTDOWN">
<!-- Service主要有两个组成部分,Engine(容器/Container)和 Connector(可以有多个,用于接收不同的请求,如http,https) -->
<Service name="Catalina">
<!-- Connector:连接器,处理连接相关的事务,通过 socket 将操作系统的连接事件转换成 Request,将servlet的返回转换成通过 socket 写出。 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- Container:容器,包含了所有servlet,以及处理request并返回Response -->
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
复制代码
Tomcat 结构说明
- server是 Tomcat 的顶级容器,表示整个 Tomcat 服务。它包含至少一个Service,用于提供具体的服务
- Service主要有两个组成部分,Engine(容器/Container)和 Connector(可以有多个,用于接收不同的请求,如http,https)
- Connector:连接器,处理连接相关的事务,通过 socket 将操作系统的连接事件转换成 Request,将servlet的返回转换成通过 socket 写出
- Container:容器,包含了所有servlet,以及处理request并返回Response
- 4.1. Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine;
- 4.2. Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点;
- 4.3. Context:代表一个应用程序,webapps下的应用程序。
- 4.4. Wrapper:每一Wrapper封装着一个Servlet;
Tomcat 启动过程
了解了 Tomcat 的基本结构之后,我们来说说Tomcat是如何启动的;
我们通过Tomcat安装目录下的 bin/startup.sh
脚本启动Tomcat服务器,最终其实是调用Bootstrap.main()
方法。main方法中启动容器的关键代码如下。
if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
复制代码
daemon.load(args);
我们配置的server.xml
在 daemon.load(args)
中被解析,将便签按父子关系按个解析并实例化。
其中 Connector
标签设置了tomcat的 链接方式,Connector
是如何解析并实例化的?
在daemon.load(args)
解析server.xml
时,会调用 ConnectorCreateRule.begin()
方法,我们来看看ta做了什么
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,并将解析到的Connector标签中的protocol属性做为入参传入
Connector con = new Connector(attributes.getValue("protocol"));
if ( ex != null ) _setExecutor(con,ex);
digester.push(con);
}
复制代码
关键性代码Connector con = new Connector(attributes.getValue("protocol"));
:初始化了一个Connector,并将解析到的Connector标签中的protocol属性做为入参传入。
让我们再看看 Connector
构造方法做了什么
public Connector(String protocol) {
setProtocol(protocol);
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
this.protocolHandler = (ProtocolHandler) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {}
if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
encodedSolidusHandling = EncodedSolidusHandling.DECODE;
}
}
----------------------------------------------------------------------------------------------->>>>>>>
public void setProtocol(String protocol) {
if (AprLifecycleListener.isAprAvailable()) {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11AprProtocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpAprProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
} else {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11AprProtocol");
}
} else {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11Protocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
}
}
复制代码
这个Connector
构造方法,根据我们配置的sever.xml
,构造了一个http协议对象。
以我们上面给出示例的 server.xml 为例(protocol=”HTTP/1.1″),盖构造方法通过反射调用org.apache.coyote.http11.Http11Protocol
类的构造方法,实例化了该对象。
public Http11Protocol() {
// 关键类,接收请求的对象和方法都在这个 JIoEndpoint 中
endpoint = new JIoEndpoint();
cHandler = new Http11ConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
复制代码
new JIoEndpoint()
类内部声明一个 Acceptor
类,该类有一个run方法,会在start时被执行。这个放在start方法中再做说明,这里先留个印象。
daemon.start();
如果说 daemon.load(args)
是解析 server.xml
并实例化各对象;那么 daemon.start()
就是 Tomcat
启动的关键所在了;
daemon.start()
通过反射调用 Catalina.start()
方法
public void start() {
// getServer 其实是通过解析server.xml中的server标签,从而设置的 StandardServer
if (getServer() == null) {
load();
}
// 这里的 start() 实则调用 startInternal();
// 调用链 依次调用 LifecycleBase的子类
try {
getServer().start();
} catch (LifecycleException e) {}
}
复制代码
getServer().start();
该方法调用 LifecycleBase.startInternal()
抽象方法,通过调用链依次调用子类实现的方法。
其中有一个比较关键的类 Connector
,是的没错ta又回来,在 load()
方法中实例化的连接器。ta在这里有发挥了什么神奇的作用呢?
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
try {
// 关键代码
protocolHandler.start();
} catch (Exception e) {}
mapperListener.start();
}
复制代码
protocolHandler
对象就是在 Connector
构造器中实例化的类org.apache.coyote.http11.Http11Protocol
,这里调用的是ta的父类 AbstractProtocol.start()
;
@Override
public void start() throws Exception {
try {
endpoint.start();
} catch (Exception ex) {}
}
复制代码
endpoint
就是我们在Http11Protocol
构造器中的JIoEndpoint
,先执行JIoEndpoint
父类的start方法,然后调用JIoEndpoint.startInternal()
;
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// 关键代码
startAcceptorThreads();
Thread timeoutThread = new Thread(new AsyncTimeout(),
getName() + "-AsyncTimeout");
timeoutThread.setPriority(threadPriority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
}
复制代码
startAcceptorThreads
方法,实例化了Acceptor
,并执行run方法;
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
复制代码
在Acceptor
的run方法中,通过IO模型,利用socket获取客户端连接并处理请求;
Tomcat 启动过程总结(基于 BIO 配置的Tomcat)
- Tomcat通过
daemon.load(args)
解析server.xml
,实例化了Engine(容器/Container)和 Connector; - Tomcat通过
daemon.start()
启动一个Acceptor
的线程,该线程用于接收 操作系统 接收到的socket请求。 - 如果没有请求进来,该线程会阻塞等待【这里说的请求是指accept请求】
- 如果接受到请求,Tomcat会再开启
SocketProcessor
线程处理请求。
Tomcat 请求处理过程
在Tomcat启动过程中,最后我们发现tomcat启动了一个 Acceptor
线程接收客户端accept请求,接收到accept请求后,tomcat启动了 SocketProcessor
线程处理其他读写请求。
那么 SocketProcessor
中是如何处理读写请求的呢?我们深入到 SocketProcessor
的run方法中一探究竟。
public void run() {
boolean launch = false;
synchronized (socket) {
try {
SocketState state = SocketState.OPEN;
try {
// https 进行 ssl 验证
serverSocketFactory.handshake(socket.getSocket());
} catch (Throwable t) {}
if ((state != SocketState.CLOSED)) {
// 关键代码块
if (status == null) {
state = handler.process(socket, SocketStatus.OPEN_READ);
} else {
state = handler.process(socket,status);
}
}
````
}
复制代码
通过 state = handler.process(socket, SocketStatus.OPEN_READ)
处理read请求
这个handler
是什么呢?其实就是我们在 daemon.load(args)
中实例化 Http11Protocol
时这设置的;
由于Http11Protocol
不存在process
方法,所以此处调用了ta的父类 AbstractHttp11Processor.process()
;
因为 AbstractHttp11Processor.process()
方法代码超多,篇幅原因此处不粘贴了…
process 方法中有一关键代码adapter.service(request, response)
;
service方法中通过connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
依次调用 Value 类的子类【StandardEngineValve
-> StandardHostValve
-> StandardContextValve
-> StandardWrapperValve
】的invoke方法;
在StandardWrapperValve.incoke()
方法中调用过滤器filterChain.doFilter(request.getRequest(), response.getResponse())
;
filterChain.doFilter
调用了 ApplicationFilterChain.internalDoFilter()
方法,internalDoFilter
中通过调用servlet.service(request, response)
;
最终调用到HttpServlet.service()
方法根据请求中的post/get等请求方式,调用doPost/doGet方法;
Tomcat 请求处理过程总结
SocketProcessor
线程在接收到读写事件时,会解析socket中的数据,并封装成request和response,并通过adapter【CoyoteAdapter】
调用service
-> StandardEngineValve
-> StandardHostValve
-> StandardContextValve
-> StandardWrapperValve
的 invoke() 方法,在StandardWrapperValve.invoke()
方法中调用过滤器【ApplicationFilterChain
】,在过滤器中调用 servlet 的 doGet/doPost
等方法