一文詳解tomcat是如何處理HTTP長連接的
1、HTTP長連接
HTTP長連接,也稱為持久連接,是一種使用同一個TCP連接來發(fā)送和接收多個HTTP請求/應(yīng)答的方法,而不是為每一個新的請求/應(yīng)答打開新的TCP連接。這種方式由于通信連接一直存在,因此可以減少建立和關(guān)閉連接的開銷,提高通信效率。因為HTTP長連接的本質(zhì)就是保持TCP的連接在每次請求響應(yīng)之后不斷開,與其說是HTTP長連接,不如說是TCP的長連接。
那么tomcat作為最常用的WEB容器,是怎么處理HTTP的長連接呢?
2、tomcat處理長連接
在tomcat的Poller線程中,監(jiān)聽已連接套接字以保持連接,并輪詢以檢查數(shù)據(jù)是否可用。具體來說,Poller線程使用NIO架構(gòu),通過內(nèi)部的Selector對象向內(nèi)核查詢Channel的狀態(tài),一旦發(fā)現(xiàn)可讀事件,就會生成任務(wù)類SocketProcessor,并將其交給Executor去處理。
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; } 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(); // Attachment may be null if another thread has called // cancelledKey() if (socketWrapper != null) { processKey(sk, socketWrapper); } } // Process timeouts timeout(keyCount,hasEvents); } getStopLatch().countDown(); }
Poller線程的run方法是while(true)死循環(huán),主要監(jiān)聽注冊的socket上是否有已就緒事件,如果有的話就調(diào)用processKey(sk, socketWrapper)方法交由線程池處理,最后調(diào)用了timeout方法。
protected void timeout(int keyCount, boolean hasEvents) { long now = System.currentTimeMillis(); // nextExpiration初始化是0 if (nextExpiration > 0 && (keyCount > 0 || hasEvents) && (now < nextExpiration) && !close) { return; } int keycount = 0; try { // 遍歷注冊到selector上所有的socket for (SelectionKey key : selector.keys()) { keycount++; NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment(); try { if (socketWrapper == null) { // We don't support any keys without attachments if (key.isValid()) { key.cancel(); } } else if (close) { key.interestOps(0); // Avoid duplicate stop calls socketWrapper.interestOps(0); socketWrapper.close(); // 如果注冊的事件是讀寫事件 } else if (socketWrapper.interestOpsHas(SelectionKey.OP_READ) || socketWrapper.interestOpsHas(SelectionKey.OP_WRITE)) { boolean readTimeout = false; boolean writeTimeout = false; // 檢查讀超時 if (socketWrapper.interestOpsHas(SelectionKey.OP_READ)) { // 用當(dāng)前時間-上次讀時間 long delta = now - socketWrapper.getLastRead(); long timeout = socketWrapper.getReadTimeout(); if (timeout > 0 && delta > timeout) { readTimeout = true; } } // Check for write timeout if (!readTimeout && socketWrapper.interestOpsHas(SelectionKey.OP_WRITE)) { long delta = now - socketWrapper.getLastWrite(); long timeout = socketWrapper.getWriteTimeout(); if (timeout > 0 && delta > timeout) { writeTimeout = true; } } // 如果已經(jīng)超時 if (readTimeout || writeTimeout) { key.interestOps(0); // Avoid duplicate timeout calls socketWrapper.interestOps(0); socketWrapper.setError(new SocketTimeoutException()); if (readTimeout && socketWrapper.readOperation != null) { if (!socketWrapper.readOperation.process()) { socketWrapper.close(); } } else if (writeTimeout && socketWrapper.writeOperation != null) { if (!socketWrapper.writeOperation.process()) { socketWrapper.close(); } // processSocket中對將socket進(jìn)行關(guān)閉 } else if (!processSocket(socketWrapper, SocketEvent.ERROR, true)) { socketWrapper.close(); } } } } catch (CancelledKeyException ckx) { if (socketWrapper != null) { socketWrapper.close(); } } } } catch (ConcurrentModificationException cme) { // See https://bz.apache.org/bugzilla/show_bug.cgi?id=57943 log.warn(sm.getString("endpoint.nio.timeoutCme"), cme); } // For logging purposes only long prevExp = nextExpiration; // nextExpiration重新賦值 當(dāng)前時間+1s,socketProperties.getTimeoutInterval()默認(rèn)1000 nextExpiration = System.currentTimeMillis() + socketProperties.getTimeoutInterval(); if (log.isTraceEnabled()) { log.trace("timeout completed: keys processed=" + keycount + "; now=" + now + "; nextExpiration=" + prevExp + "; keyCount=" + keyCount + "; hasEvents=" + hasEvents + "; eval=" + ((now < prevExp) && (keyCount>0 || hasEvents) && (!close) )); } }
timeout方法主要做了以下事:
- 判斷是否要進(jìn)行輪詢所有socket進(jìn)行超時判斷
- 遍歷所有socket,拿到上次讀寫的事件,與當(dāng)前時間對比,是否已超時
- 如果已超時,對相關(guān)socket進(jìn)行關(guān)閉處理
- 重置nextExpiration值,默認(rèn)每秒都會對所有socket進(jìn)行超時輪詢判斷
在進(jìn)行對socket讀取時會把keepAliveTimeout參數(shù)賦值給ReadTimeout(前提,開啟長連接,tomcat已經(jīng)默認(rèn)開啟長連接)
if (keptAlive) { // Haven't read any request data yet so use the keep-alive // timeout. wrapper.setReadTimeout(keepAliveTimeout); }
每次對socket進(jìn)行讀取后,也會調(diào)用updateLastRead方法更新上次讀取時間
if (to.remaining() >= limit) { to.limit(to.position() + limit); nRead = fillReadBuffer(block, to); if (log.isDebugEnabled()) { log.debug("Socket: [" + this + "], Read direct from socket: [" + nRead + "]"); } updateLastRead(); }
3、總結(jié)
tomcat處理Http長連接是在Poller線程中的timeout方法,最長每秒都會對所有的socket進(jìn)行遍歷,上次讀寫數(shù)據(jù)的時間與當(dāng)前時間和參數(shù)配置的keep-alive-timeout時間進(jìn)行判斷是否已經(jīng)超時(前提開啟長連接),如果已經(jīng)超時則對相應(yīng)的socket進(jìn)行關(guān)閉
以上就是一文詳解tomcat是如何處理HTTP長連接的的詳細(xì)內(nèi)容,更多關(guān)于tomcat處理HTTP長連接的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
idea專業(yè)版和idea社區(qū)版整合Tomcat并將war包部署
IDEA是一個功能完善的Java開發(fā)工具,除了具備有良好的代碼開發(fā)提示之外,還可以直接在IDEA中集成并啟動Tomcat實現(xiàn)程序的自動部署,本文主要介紹了idea專業(yè)版和idea社區(qū)版整合Tomcat并將war包部署,感興趣的可以了解一下2023-11-11Tomcat6.0與windows 2003 server 的IIS服務(wù)器集成
本例主要講解Tomcat6.0與windows 2003 server 的IIS服務(wù)器集成的問題,用到的工具版 本如下:jdk是6.0、Tomcat 6.0、windows 2003 server 的IIS。2009-08-08Tomcat Nginx Redis實現(xiàn)session共享過程圖解
這篇文章主要介紹了Tomcat Nginx Redis實現(xiàn)session共享過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07tomcat logs 目錄下各日志文件的解析(小結(jié))
這篇文章主要介紹了tomcat logs 目錄下各日志文件的含義,包括catalina.日期.log,commons-daemon.日期.log,host-manager.日期.log,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-12-12