Tomcat處理請(qǐng)求的線程模型詳解
一、前言
JAVA后端項(xiàng)目,運(yùn)行在容器tomcat中,由于現(xiàn)在springboot的內(nèi)置tomcat容器,其默認(rèn)配置屏蔽了很多對(duì)tomcat的認(rèn)知,但是對(duì)tomcat的學(xué)習(xí)和認(rèn)識(shí)是比較重要的,所以專(zhuān)門(mén)查資料加深了理解,本文主要討論在springboot集成下的tomcat9的請(qǐng)求過(guò)程,線程模型為NIO。
二、tomcat結(jié)構(gòu)

找了張結(jié)構(gòu)圖,每個(gè)模塊的意思和作用就不詳解了,可以搜其他文章
三、探討tomcat是如何處理請(qǐng)求

自己畫(huà)了一個(gè)connector的結(jié)構(gòu)
1、初始化
在springboot啟動(dòng)后,org.springframework.context.support.AbstractApplicationContext#finishRefresh,這里進(jìn)去調(diào)用org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle.start()方法啟動(dòng)TomcatWebServer,初始化tomcat。


通過(guò)這樣的調(diào)用鏈到達(dá)org.apache.tomcat.util.net.NioEndpoint#startInternal(),進(jìn)行初始化Endpoint中的Acceptor和Poller,這兩者都實(shí)現(xiàn)了Runnable接口,初始化后就通過(guò)線程start啟動(dòng)了。
2、如何處理客戶(hù)端請(qǐng)求
Acceptor: 接收器,作用是接受scoket網(wǎng)絡(luò)請(qǐng)求,并調(diào)用setSocketOptions()封裝成為NioSocketWrapper,并注冊(cè)到Poller的events中。注意查看run方法org.apache.tomcat.util.net.Acceptor#run
@Override
public void run() {
int errorDelay = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
// Loop if endpoint is paused
while (endpoint.isPaused() && !stopCalled) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (stopCalled) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null;
try {
// 等待下一個(gè)請(qǐng)求進(jìn)來(lái)
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;
}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {
// 注冊(cè)socket到Poller,生成PollerEvent事件
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
} finally {
stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
Poller:輪詢(xún)器,輪詢(xún)是否有事件達(dá)到,有請(qǐng)求事件到達(dá)后,以NIO的處理方式,查詢(xún)Selector取出所有請(qǐng)求,遍歷每個(gè)請(qǐng)求的需求,分配給Executor線程池執(zhí)行。查看org.apache.tomcat.util.net.NioEndpoint.Poller#run()
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
wakeupCounter.set(0);
}
if (close) {
events();
timeout(0, false);
try {
selector.close();
} catch (IOException ioe) {
log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
}
break;
}
// Either we timed out or we woke up, process events first
if (keyCount == 0) {
hasEvents = (hasEvents | events());
}
} catch (Throwable x) {
ExceptionUtils.handleThrowable(x);
log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
continue;
}
//查詢(xún)selector取出所有請(qǐng)求
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
//處理請(qǐng)求key
if (socketWrapper != null) {
processKey(sk, socketWrapper);
}
}
// Process timeouts
timeout(keyCount,hasEvents);
}
getStopLatch().countDown();
}
請(qǐng)求過(guò)程大致如下圖:

總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java初學(xué)者之五子棋游戲?qū)崿F(xiàn)教程
這篇文章主要為大家詳細(xì)介紹了Java初學(xué)者之五子棋游戲?qū)崿F(xiàn)教程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
gradle項(xiàng)目中資源文件的相對(duì)路徑打包技巧必看
這篇文章主要介紹了gradle項(xiàng)目中資源文件的相對(duì)路徑打包技巧必看篇,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
java中vector與hashtable操作實(shí)例分享
java中vector與hashtable操作實(shí)例,有需要的朋友可以參考一下2014-01-01
Spring boot整合連接池實(shí)現(xiàn)過(guò)程圖解
這篇文章主要介紹了Spring boot整合連接池實(shí)現(xiàn)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
PowerJob的HashedWheelTimer工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的HashedWheelTimer工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
利用Spring Data MongoDB持久化文檔數(shù)據(jù)的方法教程
這篇文章主要給大家介紹了關(guān)于利用Spring Data MongoDB持久化文檔數(shù)據(jù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08

