[Tomcat原始碼系列] Tomcat Connector
Connector是Tomcat最核心的元件之一,負責處理一個WebServer最核心的連線管理、Net IO、執行緒(可選)、協議解析和處理的工作。
一、聯結器介紹
在開始Connector探索之路之前,先看看Connector幾個關鍵字
- NIO:Tomcat可以利用Java比較新的NIO技術,提升高併發下的Socket效能
- AJP:Apache JServ Protocol,AJP的提出當然還是為了解決java亙古不變的問題——效能,AJP協議是基於包的長連線協議,以減少前端Proxy與Tomcat連線Socket連線建立的代價,目前Apache通過JK和AJP_ROXY的方式支援AJP協議,需要注意的是,雖然Nginx作為代理伺服器效能強勁,但其只能通過HTTP PROXY的方式與後端的Tomcat聯絡,因此如果從作為代理伺服器的角度上講,在這種情況下Nginx未必會比Apache體現出更優的效能
- APR/Native:Apache Portable Runtime,還是一個詞,效能。APR的提出利用Native程式碼更好地解決效能問題,更好地與本地伺服器(linux)打交道。讓我們看看Tomcat文件對APR的介紹
These features allows making Tomcat a general purpose webserver, will enable much better integration with other native web technologies, and overall make Java much more viable as a full fledged webserver platform rather than simply a backend focused technology.
通過對如上名詞的組合,Tomcat組成了如下的Connector系列:
- Http11Protocol:支援HTTP1.1協議的聯結器
- Http11NioProtocol:支援HTTP1.1 協議+ NIO的聯結器
- Http11AprProtocol:使用APR技術處理連線的聯結器
- AjpProtocol:支援AJP協議的聯結器
- AjpAprProtocol:使用APR技術處理連線的聯結器
二、範例
我們以最簡單的Http11Protocol為例,看看從請求進來到處理完畢,聯結器部件是處理處理的。首先我們利用Tomcat元件組成我們一個最簡單的WebServer,其具備如下功能:
- 監停某個埠,接受客戶端的請求,並將請求分配給處理執行緒
- 處理執行緒處理請求,分析HTTP1.1請求,封裝Request/Response物件,並將請求由請求處理器處理
- 實現最簡單的請求處理器,向客戶端列印Hello World
程式碼非常簡單,首先是主功能(這裡,我們利用JDK5.0的執行緒池,聯結器不再管理執行緒功能):
package ray.tomcat.test;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.coyote.http11.Http11Protocol;
public class TomcatMainV2
{
public static void main(String[] args) throws Exception
{
Http11Protocol protocol = new Http11Protocol();
protocol.setPort(8000);
ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();
threadPoolExecutor.prestartCoreThread();
protocol.setExecutor(threadPoolExecutor);
protocol.setAdapter(new MyHandler());
protocol.init();
protocol.start();
}
public static ThreadPoolExecutor createThreadPoolExecutor()
{
int corePoolSize = 2;
int maximumPoolSize = 10;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
return threadPoolExecutor;
}
}
請求處理器向客戶端打引Hello World,程式碼如下
package ray.tomcat.test;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import org.apache.coyote.Adapter;
import org.apache.coyote.Request;
import org.apache.coyote.Response;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.net.SocketStatus;
public class MyHandler implements Adapter
{
//支援Comet,Servlet3.0將對Comet提供支援,Tomcat6目前是非標準的實現
public boolean event(Request req, Response res, SocketStatus status)
throws Exception
{
System.out.println("event");
return false;
}
//請求處理
public void service(Request req, Response res) throws Exception
{
System.out.println("service");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));
writer.println("Hello World");
writer.flush();
ByteChunk byteChunk = new ByteChunk();
byteChunk.append(baos.toByteArray(), 0, baos.size());
res.doWrite(byteChunk);
}
}
執行主程式,在瀏覽器中輸入http://127.0.0.1:8000,我們可以看到列印”Hello World”
三、分析
以如上Http11Protocol為例,我們可以看到,Tomcat實現一個最簡單的處理Web請求的程式碼其實非常簡單,其主要包括如下核心處理類:
- Http11Protocol:Http1.1協議處理入口類,其本身沒有太多邏輯,對請求主要由JIoEndPoint類處理
- Http11Protocol$Http11ConnectionHandler:連線管理器,管理連線處理佇列,並分配Http11Processor對請求進行處理
- Http11Processor:請求處理器,負責HTTP1.0協議相關的工作,包括解析請求和處理響應資訊,並呼叫Adapter做實際的處理工作,如上我們看到了我們自定義的Adapter實現響應”Hello World”
- JIoEndPoint:監停埠,啟動接受執行緒準備接收請求,在請求接受後轉給工作執行緒處理
- JIoEndPoint$Acceptor:請求接收器,接收後將Socket分配給工作執行緒繼續後續處理
- JIoEndPoint$Worker:工作執行緒,使用Handler來處理請求,對於我們的HTTP1.1協議來說,其實現是Http11Protocol$Http11ConnectionHandler。這部分不是必須的,也可以選擇JDK的concurrent包的執行緒池
實際上各種聯結器實現基本大同小異,基本上都是由如上部分組合而成
1.初始化:首先,還是從入口開始,先看看初始化init
public void init() throws Exception {
endpoint.setName(getName());
endpoint.setHandler(cHandler); //請求處理器,對於HTTP1.1協議,是Http11Protocol$Http11ConnectionHandler
// 初始化ServerSocket工廠類,如果需SSL/TLS支援,使用JSSESocketFactory/PureTLSSocketFactory
. . . (略)
//主要的初始化過程實際是在endpoint(JIoEndpoint)
endpoint.init();
. . . (略)
}
Http11Protocol的初始化非常簡單,準備好ServerSocket工廠,呼叫JIoEndPoint的初始化。讓我們接下來看看JIoEndPoint的初始化過程
public void init()
throws Exception {
if (initialized)
return;
// Initialize thread count defaults for acceptor
// 請求接收處理執行緒,這個值實際1已經足夠
if (acceptorThreadCount == 0) {
acceptorThreadCount = 1;
}
if (serverSocketFactory == null) {
serverSocketFactory = ServerSocketFactory.getDefault();
}
//建立監停ServerSocket,port為監聽埠,address為監停地址
// backlog為連線請求佇列容量(@param backlog how many connections are queued)
if (serverSocket == null) {
try {
if (address == null) {
serverSocket = serverSocketFactory.createSocket(port, backlog);
} else {
serverSocket = serverSocketFactory.createSocket(port, backlog, address);
}
} catch (BindException be) {
throw new BindException(be.getMessage() + ":" + port);
}
}
//if( serverTimeout >= 0 )
// serverSocket.setSoTimeout( serverTimeout );
initialized = true;
}
可以看到,監停埠在此處準備就緒
public void start()
throws Exception {
// Initialize socket if not done before
if (!initialized) {
init();
}
if (!running) {
running = true;
paused = false;
// Create worker collection
// 初始化工作執行緒池,有WorkerStack(Tomcat自實現)和Executor(JDK concurrent包)兩種實現
if (executor == null) {
workers = new WorkerStack(maxThreads);
}
// Start acceptor threads
// 啟動請求連線接收處理執行緒
for (int i = 0; i < acceptorThreadCount; i++) {
Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
acceptorThread.setPriority(threadPriority);
acceptorThread.setDaemon(daemon); //設定是否daemon引數,預設為true
acceptorThread.start();
}
}
}
2.準備好連線處理:初始化完畢,準備好連線處理,準備接收連線上來,同樣的,Http11Protocol的start基本沒幹啥事,呼叫一下JIoEndPoint的start,我們來看看JIoEndPoint的start
public void start()
throws Exception {
// Initialize socket if not done before
if (!initialized) {
init();
}
if (!running) {
running = true;
paused = false;
// Create worker collection
// 初始化工作執行緒池,有WorkerStack(Tomcat自實現)和Executor(JDK concurrent包)兩種實現
if (executor == null) {
workers = new WorkerStack(maxThreads);
}
// Start acceptor threads
// 啟動請求連線接收處理執行緒
for (int i = 0; i < acceptorThreadCount; i++) {
Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
acceptorThread.setPriority(threadPriority);
acceptorThread.setDaemon(daemon); //設定是否daemon引數,預設為true
acceptorThread.start();
}
}
}
主要處理的事情無非就是準備和工作執行緒(處理具體請求的執行緒度池,可選,也可以使用JDK5.0的執行緒池),連線請求接收處理執行緒(程式碼中,一般acceptorThreadCount=1)
3.連線請求接收處理:準備就緒,可以連線入請求了。現在工作已經轉到了Acceptor(JIoEndPoint$Acceptor)這裡,我們看看Acceptor到底做了些啥
public void run() {
// Loop until we receive a shutdown command
while (running) {
. . . (略)
//阻塞等待客戶端連線
Socket socket = serverSocketFactory.acceptSocket(serverSocket);
serverSocketFactory.initSocket(socket);
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
// Close socket right away
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
. . . (略)
}
}
. . . (略)
protected boolean processSocket(Socket socket) {
try {
//由工作執行緒繼續後續的處理
if (executor == null) {
getWorkerThread().assign(socket);
} else {
executor.execute(new SocketProcessor(socket));
}
} catch (Throwable t) {
. . . (略)
return false;
}
return true;
}
實際上也沒有什麼複雜的工作,無非就是有連線上來之後,將連線轉交給工作執行緒(SocketProcessor)去處理
4.工作執行緒:SocketProcessor
public void run() {
// Process the request from this socket
if (!setSocketOptions(socket) || !handler.process(socket)) {
// Close socket
try {
socket.close();
} catch (IOException e) {
}
}
// Finish up this request
socket = null;
}
工作執行緒主要是設定一下Socket引數,然後將請求轉交給handler去處理,需要注意一下如下幾個連線引數的意義:
- SO_LINGER:若設定了SO_LINGER並確定了非零的超時間隔,則closesocket()呼叫阻塞程式,直到所剩資料傳送完畢或超時。這種關閉稱為“優雅的”關 閉。請注意如果套介面置為非阻塞且SO_LINGER設為非零超時,則closesocket()呼叫將以WSAEWOULDBLOCK錯誤返回。若在一個流類套介面上設定了SO_DONTLINGER,則closesocket()呼叫立即返回。但是,如果可能,排隊的資料將在套介面關閉前傳送。請注意,在這種情況下WINDOWS套介面實現將在 一段不確定的時間內保留套介面以及其他資源(TIME_WAIT),這對於想用所以套介面的應用程式來說有一定影響。預設此引數不開啟
- TCP_NODELAY:是否開啟Nagle,預設開啟,使用Nagle演算法是為了避免多次傳送小的分組,而是累計到一定程度或者超過一定時間後才一起傳送。對於AJP連線,可能需要關注一下這個選項。
- SO_TIMEOUT:JDK API註釋如下,With this option set to a non-zero timeout,a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0。預設設定的是60秒
關於預設的設定,可以參見org.apache.coyote.http11.Constants定義
5.最終請求終於回到了Handler,此處的Handler實現是org.apache.coyote.http11.Http11Processor,其主要處理一些HTTP協議性細節的東西,此處程式碼不再列出,有興趣可以自行讀程式碼。最終請求終於回到了我們的Adapter物件,一個請求處理完畢,功德圓滿。
相關文章
- 【Tomcat 原始碼系列】原始碼構建 TomcatTomcat原始碼
- 【Tomcat 原始碼系列】認識 TomcatTomcat原始碼
- tomcat原始碼分析(第三篇 tomcat請求原理解析--Connector原始碼分析)Tomcat原始碼
- 【Tomcat 原始碼系列】Tomcat 整體結構Tomcat原始碼
- 詳解Tomcat系列(一)-從原始碼分析Tomcat的啟動Tomcat原始碼
- 【Tomcat】Tomcat原始碼閱讀之StandardHost與HostConfig的分析Tomcat原始碼
- [Web Server]Tomcat調優之SpringBoot內嵌Tomcat原始碼分析WebServerTomcatSpring Boot原始碼
- Java網路程式設計與NIO詳解11:Tomcat中的Connector原始碼分析(NIO)Java程式設計Tomcat原始碼
- Tomcat 中的 NIO 原始碼分析Tomcat原始碼
- tomcat nio2原始碼分析Tomcat原始碼
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- tomcat8.5原始碼構建Tomcat原始碼
- 介紹tomcat Connector 引數優化說明Tomcat優化
- Tomcat詳解系列(3) - 原始碼分析準備和分析入口Tomcat原始碼
- Tomcat的啟停指令碼原始碼解析Tomcat指令碼原始碼
- tomcat原始碼分析(第四篇 tomcat請求處理原理解析--Container原始碼分析)Tomcat原始碼AI
- tomcat原始碼分析(第二篇 tomcat啟動過程詳解)Tomcat原始碼
- Tomcat原始碼分析 (四)----- Pipeline和ValveTomcat原始碼
- 如何斷點除錯Tomcat原始碼斷點除錯Tomcat原始碼
- Tomcat詳解系列(2) - 理解Tomcat架構設計Tomcat架構
- 深入淺出Tomcat系列Tomcat
- Tomcat系列漏洞復現Tomcat
- The Tomcat connector configured to listen on port 80 failed to start. The port may already be inTomcatAI
- Tomcat長輪詢原理與原始碼解析Tomcat原始碼
- tomcat8.5.57原始碼閱讀筆記1Tomcat原始碼筆記
- ARTS第十三週(閱讀Tomcat原始碼)Tomcat原始碼
- [Web][Tomcat]Tomcat相關WebTomcat
- SpringBoot原始碼解析-內嵌Tomcat容器的啟動Spring Boot原始碼Tomcat
- Tomcat原始碼分析 (三)----- 生命週期機制 LifecycleTomcat原始碼
- spring boot 載入web容器tomcat流程原始碼分析Spring BootWebTomcat原始碼
- Tomcat程式碼實現Tomcat
- SpringBoot原始碼學習4——SpringBoot內嵌Tomcat啟動流程原始碼分析Spring Boot原始碼Tomcat
- Tomcat執行緒模型 BIO模型原始碼與調優Tomcat執行緒模型原始碼
- TomcatTomcat
- 深入淺出Tomcat/4 - Tomcat容器Tomcat
- 【Tomcat】手寫迷你版TomcatTomcat
- 使用Tomcat Native提升Tomcat IO效率Tomcat
- 【Tomcat】Tomcat原理與系統架構Tomcat架構
- 死磕Tomcat系列(1)——整體架構Tomcat架構