Tomcat原始碼分析2 之 Protocol實現分析
簡介
本文繼續以tomcat8 為例,簡單分析下Tomcat的幾種protocol的差異,以及處理連結的細節。
主要有以下內容:</br>
- tomcat protocol的分類</br>
- tomcat protocol的實現</br>
- 各個protocol的差異</br>
Tomcat protocol 配置
參考官方文件,tomcat protocol配置,可以看到protocol
主要是有四種,預設使用的HTTP/1.1 ,對於tomcat8 以更高版本來說,HTTP/1.1
的配置會預設使用nio來處理,也就是org.apache.coyote.http11.Http11NioProtocol
。
其他的幾種:</br>
org.apache.coyote.http11.Http11Protocol
java的bio connector,使用ServerSocket
處理請求。</br>
org.apache.coyote.http11.Http11NioProtocol
java的nio connector,使用SocketChannel
處理請求.</br>
org.apache.coyote.http11.Http11Nio2Protocol
java7新出的aio connector,使用AsynchronousSocketChannel
處理請求。</br>
org.apache.coyote.http11.Http11Nio2Protocol
tomcat的native library connector。(這個我們稍後再講)</br>
對於tomcat8 以更高版本來說,HTTP/1.1的配置會預設使用nio來處理,也就是org.apache.coyote.http11.Http11NioProtocol
。由org.apache.catalina.connector.Connector
的setProtocol()
方法也可以看到:
public void setProtocol(String protocol) {
//如果配置了apr,會預設使用apr(apr後面再講)
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 {
//這裡可以看到,HTTP/1.1是server.xml的預設配置,會預設使用nio處理
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11NioProtocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpNioProtocol");
} else if (protocol != null) {
//其他情況下,使用指定的protocol
setProtocolHandlerClassName(protocol);
}
}
}
Tomcat protocol的處理流程
在Connector
的startInternal()
方法會啟動protocol
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
...
try {
protocolHandler.start();
} catch (Exception e) {
...
}
}
這裡的protocolHandler,就是上面setProtocol()方法指定的protocol。暫時以Http11NioProtocol
為例,分析下請求處理流程。上面startInternal()
方法接下來會到org.apache.coyote.AbstractProtocol
的start()
方法。
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.start",
getName()));
try {
//每個protocol都有一個對應的enpoint,最終是由endpoint來負責處理連結的。
endpoint.start();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.startError",
getName()), ex);
throw ex;
}
}
每個protocol對應了一個Endpoint</br>
Http11Protocol
對應org.apache.tomcat.util.net.JIoEndpoint
</br>
Http11NioProtocol
對應org.apache.tomcat.util.net.NioEndpoint
</br>
Http11Nio2Protocol
對應org.apache.tomcat.util.net.Nio2Endpoint
</br>
繼續檢視Nio2Endpoint
的startInternal()
方法.
@Override
public void startInternal() throws Exception {
if (!running) {
...
//初始化最大連線數限制,在server.xml中可配置
initializeConnectionLatch();
// Start poller threads
// poller 主要負責檢查各個 Selector 的狀態以及處理超時等
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
//啟動接受連結的執行緒
startAcceptorThreads();
}
}
startAcceptorThreads()
方法會啟動Endpoint
內部的Acceptor
。繼續檢視org.apache.tomcat.util.net.NioEndpoint.Acceptor
的run()
方法:
@Override
public void run() {
...
// Loop until we receive a shutdown command
while (running) {
...
try {
//檢查下當前是否已經達到了最大連結數
//if we have reached max connections, wait
countUpOrAwaitConnection();
SocketChannel socket = null;
try {
// Accept the next incoming connection from the server socket
socket = serverSock.accept();
} catch (IOException ioe) {
...
}
// Successful accept, reset the error delay
errorDelay = 0;
// setSocketOptions() will add channel to the poller if successful
// setSocketOptions 會把channel新增到poller
if (running && !paused) {
if (!setSocketOptions(socket)) {
countDownConnection();
closeSocket(socket);
}
} else {
countDownConnection();
closeSocket(socket);
}
} ...
}
state = AcceptorState.ENDED;
}
}
從程式碼中可以看到,使用 SocketChannel 接受連線,然後通過 setSocketOptions 方法把 channel 交給 poller 處理。setSocketOptions(SocketChannel socket)
方法則是真正處理連結的地方。
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//disable blocking, APR style, we are gonna be polling it
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
NioChannel channel = nioChannels.pop();
...
//會把 呼叫poller的 register 方法,把 channel 交給 poller
getPoller0().register(channel);
} catch (Throwable t) {
...
}
return true;
}
getPoller0() 會根據設定的poll執行緒數,返回一個當前可用的 Poller 物件,使用Round robin演算法實現一個簡單的負載均衡。
public Poller getPoller0() {
int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
return pollers[idx];
}
接下來檢視 register()
方法
public void register(final NioChannel socket) {
socket.setPoller(this);
// 新建一個 KeyAttachment 物件,儲存跟socket相關的一些資訊
KeyAttachment ka = new KeyAttachment(socket);
ka.setPoller(this);
ka.setTimeout(getSocketProperties().getSoTimeout());
ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
ka.setSecure(isSSLEnabled());
PollerEvent r = eventCache.pop();
ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
else r.reset(socket,ka,OP_REGISTER);
//比較重要的是這句,把 r 新增到當前的 events 佇列中。
addEvent(r);
}
上面可以看到,每個請求進來之後,會通過 register 方法建立一個 PollerEvent 物件並新增到當前的 SynchronizedQueue<PollerEvent> events 佇列中。
接下來看 Poller的run()
方法,上面已經說過,Poller 在startInternal()
方法呼叫的時候建立並啟動。
@Override
public void run() {
// Loop until destroy() is called
while (true) {
try {
...
boolean hasEvents = false;
// Time to terminate?
if (close) {
events();
timeout(0, false);
...
break;
} else {
//呼叫 events() 方法
hasEvents = events();
}
}
...
}//while
stopLatch.countDown();
}
接下來檢視events()方法
public boolean events() {
boolean result = false;
PollerEvent pe = null;
while ( (pe = events.poll()) != null ) {
result = true;
try {
//呼叫 PollerEvent 的 run() 方法
pe.run();
pe.reset();
if (running && !paused) {
eventCache.push(pe);
}
} catch ( Throwable x ) {
log.error("",x);
}
}
return result;
}
可以看到,在tomcat啟動之後,會啟動相應的endpoint的Acceptor來接受請求,同時啟動相應的Poller來處理請求。</br>
PollerEvent的run方法
@Override
public void run() {
//對於新增的連結,會註冊給 poller 的 selector 處理。
if ( interestOps == OP_REGISTER ) {
try {
//把當前的key註冊給poller中的selector物件,準備後續處理。
socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
} catch (Exception x) {
log.error("", x);
}
} else {
...
}//end if
}//run
再回過來看 Poller 的 run() 方法。裡面有一段
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
KeyAttachment attachment = (KeyAttachment)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
attachment.access();
iterator.remove();
processKey(sk, attachment);
}
}//while
會檢查selector
所有的key,然後處理呼叫processKey()
進行處理。processKey()
會根據配置的Executer
呼叫SocketProcessor
的 run()
方法.
接下來看SocketProcessor
的run()
方法最終會呼叫doRun()
方法,在doRun()
裡有如下程式碼:
if (status == null) {
state = handler.process(ka, SocketStatus.OPEN_READ);
} else {
state = handler.process(ka, status);
}
接下來會呼叫到handler
的process
方法,這個handler就是在Http11NioProtocol
的構造方法裡由setHandler
設定的handler,也就是Http11ConnectionHandler
繼承了 AbstractConnectionHandler
,接著來到 org.apache.coyote.AbstractProtocol.AbstractConnectionHandler
的 process
方法,這裡會對每個socket做處理。包括呼叫具體的servlet處理業務等等。
總結
tomcat在啟動的時候,根據配置的protocol,啟動不同的Endpoint中的Acceptor 和 Poller。Acceptor負責接受請求,Poller負責呼叫執行緒池執行業務。
相關文章
- Thrift之Protocol原始碼分析Protocol原始碼
- tomcat nio2原始碼分析Tomcat原始碼
- HashMap原始碼實現分析HashMap原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- JDK中的BitMap實現之BitSet原始碼分析JDK原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- redis個人原始碼分析2---dict的實現原理Redis原始碼
- Tomcat 中的 NIO 原始碼分析Tomcat原始碼
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- TOMCAT原始碼分析(啟動框架)Tomcat原始碼框架
- JVM原始碼分析之Object.wait/notify實現JVM原始碼ObjectAI
- Spring原始碼分析之 lazy-init 實現原理Spring原始碼
- 【Tomcat】Tomcat原始碼閱讀之StandardHost與HostConfig的分析Tomcat原始碼
- tomcat原始碼分析(第三篇 tomcat請求原理解析--Connector原始碼分析)Tomcat原始碼
- 精盡Spring Boot原始碼分析 - 內嵌Tomcat容器的實現Spring Boot原始碼Tomcat
- Mysql原始碼分析2MySql原始碼
- HashMap 實現原理與原始碼分析HashMap原始碼
- HashMap實現原理及原始碼分析HashMap原始碼
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- Spark原始碼分析之DiskBlockMangaer分析Spark原始碼BloC
- Spark原始碼分析之cahce原理分析Spark原始碼
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之AbstractQueuedSynchronizer原始碼
- 原始碼分析之ArrayList原始碼
- [Web Server]Tomcat調優之SpringBoot內嵌Tomcat原始碼分析WebServerTomcatSpring Boot原始碼
- JVM原始碼分析之Attach機制實現完全解讀JVM原始碼
- spark 原始碼分析之十四 -- broadcast 是如何實現的?Spark原始碼AST
- 精盡Spring Boot原始碼分析 - 支援外部 Tomcat 容器的實現Spring Boot原始碼Tomcat
- tomcat原始碼分析(第四篇 tomcat請求處理原理解析--Container原始碼分析)Tomcat原始碼AI
- Struts2 原始碼分析-----工作原理分析原始碼
- 從kratos分析BBR限流原始碼實現原始碼
- 《Spring原始碼分析》IOC的實現Spring原始碼
- musl中strlen原始碼實現和分析原始碼
- TCC-Transaction 原始碼分析 —— TCC 實現原始碼
- ConcurrentHashMap 實現原理和原始碼分析HashMap原始碼
- 【MyBatis原始碼分析】外掛實現原理MyBatis原始碼
- Redux原始碼分析(2) - createStoreRedux原始碼