目錄
前面幾篇文章講解了Tomcat如何啓動的,如何通過NIO,開啓一個ServerSocket服務監聽。
最後用戶通過瀏覽器就可以直接訪問到想要的資源。
下圖中Connector部分:
一個Connector將在某個指定端口上偵聽客戶請求,並將獲得的請求交給Engine來處理,從Engine處獲得迴應並返回客戶。 Tomcat有兩個典型的Connector: - 一個直接偵聽來自browser的http請求 - 一個偵聽來自其它WebServer的請求 Coyote Http/1.1 Connector 在端口8080處偵聽來自客戶browser的http請求。 Coyote JK2 Connector 在端口8009處偵聽來自其它WebServer(Apache)的servlet/jsp代理請求。
請求到達tomcat 的執行流程
請求如何到達Tomcat:
1.我們發起一個瀏覽器的請求。
2.請求會被之前tomcat啓動後的ServerSocket監聽到(這是通過TCP/UDP傳輸協議實現),
Connector將請求交給Engin ---> Host --->Context 一層層過濾最後到達具體的Servlet。
3.Servlet處理完業務最後將請求返回給Connector,返回給用戶。
在進入容器 後如何執行的
將上圖進行展開來看一下 :
Tomcat中的六個容器:
容器 | 作用 |
---|---|
Server容器 | 一個StandardServer類實例就表示一個Server容器,server是tomcat的頂級構成容器 |
Service容器 | 一個StandardService類實例就表示一個Service容器,Tomcat的次頂級容器,Service是這樣一個集合:它由一個或者多個Connector組成,以及一個Engine,負責處理所有Connector所獲得的客戶請求。 |
Engine容器 | 一個StandardEngine類實例就表示一個Engine容器。Engine下可以配置多個虛擬主機Virtual Host,每個虛擬主機都有一個域名。當Engine獲得一個請求時,它把該請求匹配到某個Host上,然後把該請求交給該Host來處理,Engine有一個默認虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該默認Host來處理 |
Host容器 | 一個StandardHost類實例就表示一個Host容器,代表一個VirtualHost,虛擬主機,每個虛擬主機和某個網絡域名Domain Name相匹配。每個虛擬主機下都可以部署(deploy)一個或者多個WebApp,每個Web App對應於一個Context,有一個Context path。當Host獲得一個請求時,將把該請求匹配到某個Context上,然後把該請求交給該Context來處理。匹配的方法是“最長匹配”,所以一個path==”“的Context將成爲該Host的默認Context。所有無法和其它Context的路徑名匹配的請求都將最終和該默認Context匹配 |
Context容器 | 一個StandardContext類實例就表示一個Context容器。一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成。Context在創建的時候將根據配置文件CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml載入Servlet類。當Context獲得請求時,將在自己的映射表(mappingtable)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的迴應,並返回 |
Wrapper容器 | 一個StandardWrapper類實例就表示一個Wrapper容器,Wrapper容器負責管理一個Servlet,包括Servlet的裝載、初始化、資源回收。Wrapper是最底層的容器,其不能在添加子容器了。Wrapper是一個接口,其標準實現類是StandardWrapper |
Container用於封裝和管理Servlet,以及具體處理Request請求,在Connector內部包含了4個子容器,結構圖如下
四個子容器的作用分別是:
- (1)Engine:引擎,用來管理多個站點,一個Service最多只能有一個Engine;
- (2)Host:代表一個站點,也可以叫虛擬主機,通過配置Host就可以添加站點;
- (3)Context:代表一個應用程序,對應着平時開發的一套程序,或者一個WEB-INF目錄以及下面的web.xml文件;
- (4)Wrapper:每一Wrapper封裝着一個Servlet。
類中的執行流程:
四個主要容器的執行流程:
Container處理請求是使用Pipeline-Valve管道來處理的!(Valve是閥門之意)
Pipeline-Valve是責任鏈模式,責任鏈模式是指在一個請求處理的過程中有很多處理者依次對請求進行處理,每個處理者負責做自己相應的處理,處理完之後將處理後的請求返回,再讓下一個處理着繼續處理。
代碼從 NioEndpoint開始
1. 前面的文章講過如何啓動了一個NIO的server
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = endpoint.serverSocketAccept();.............
2. 啓動了server後,監聽來自瀏覽器請求
當請求到達tomcat後 ,做爲Socket接收者Acceptor會接受所有的請求,最爲Socket輪詢者Poller,Poller 會負責輪詢上述產生的事件,將就緒的事件生成 SokcetProcessor ,交給Excutor去執行。.
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
// 遍歷SelectionKey,並調度活動事件,這個過程跟普通的nio類似
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
// 獲取NioSocketWrapper,如果獲取爲空,說明其他線程調用了cancelKey()
// 則去除這個Key,否則調用processKey()
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
LogPropertiesTest.debug(" -------------- 18、Poller : 執行 processKey(sk, attachment); 方法, 執行類 :"+this.getClass());
processKey(sk, attachment); // 處理SelectKey 重要方法 !!!!!!!
}
}//while
我們可以看到這一行代碼
processKey(sk, attachment); // 處理SelectKey 重要方法 !!!!!!!
此處遍歷SelectionKey,並調度活動事件,這個過程跟普通的nio類似,開始處理一個SelectionKeyprocessKey()這個方法主要通過調用processSocket()方法創建一個SocketProcessor,然後丟到Tomcat線程池中去執行。每個Endpoint都有自己的SocketProcessor實現,
從Endpoint的屬性中可以看到,這個Processor也有緩存機制。
總結一下Poller所做的事:遍歷PollerEvents隊列,將每個事件中的通道感興趣的事件註冊到Selector,當事件就緒時,創建一個SocketProcessor或者從緩存中取出一個SocketProcessor,
然後放到線程池執行或者直接執行它的run方法執行。
protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
try {
if ( close ) {
cancelledKey(sk);
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
//此處判斷是否可讀
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
} else {
//invalid key
cancelledKey(sk);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("",t);
}
}
看到此行:
if (!processSocket(attachment, SocketEvent.OPEN_READ, true))
調用了父類AbstractEndpoint方法:
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
最終進入 SocketProcessor 的doRun()方法:
代碼中已經給了註解解釋。
@Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
// 這裏的 handshake 是用來標記https的握手完成情況,
// 如果是http不需要該握手階段,在從 11111111111 處直接置爲0
int handshake = -1;
try {
if (key != null) {
if (socket.isHandshakeComplete()) { // 1111111111
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
// 如果不能完成TLS握手過程,標記握手失敗
handshake = -1;
} else {
// 處理https的SecureNioChannel覆寫了該hanshake()方法
// 返回註冊的SelectionKey
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) { // 握手完成
SocketState state = SocketState.OPEN; // 標記Socket狀態
// Process the request from this socket
// 處理該Sockt裏的請求
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key); // 否則關閉通道
}
} else if (handshake == -1 ) { // 握手失敗則關閉通道
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){ // TLS會走到這裏
socketWrapper.registerReadInterest(); // 註冊讀就緒
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest(); // 註冊寫就緒
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error("", t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this); // 將SocketProcessor放回緩存中
}
}
}
}
簡而言之 重要的代碼還是看這幾行:
// 處理該Sockt裏的請求
if (event == null) {
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
方法再次跳轉進入了
@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractConnectionHandler.process",
wrapper.getSocket(), status));
}
..........
do {
//此處是重點方法調用
state = processor.process(wrapper, status);
.........
}
.......
}
隨後進入了 AbstractProcessorLight 類的 process(SocketWrapperBase<?> socketWrapper, SocketEvent status) 方法。
後再經過調用便進入
CoyoteAdapter類的service方法調用
此方法創建了Request、 Response,並且對Request進行了解析。
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringCharset(connector.getURICharset());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean async = false;
boolean postParseSuccess = false;
req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
try {
// 解析 Request
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) { //解析成功後進入 Sevlet的調用過程。
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener();
if (readListener != null && request.isFinished()) {
// Possible the all data may have been read during service()
// method so this needs to be checked here
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}
Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// If an async request was started, is not going to end once
// this container thread finishes and an error occurred, trigger
// the async error process
if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} finally {
AtomicBoolean error = new AtomicBoolean(false);
res.action(ActionCode.IS_ERROR, error);
if (request.isAsyncCompleting() && error.get()) {
// Connection will be forcibly closed which will prevent
// completion happening at the usual point. Need to trigger
// call to onComplete() here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
async = false;
}
// Access log
if (!async && postParseSuccess) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
Context context = request.getContext();
Host host = request.getHost();
// If the context is null, it is likely that the endpoint was
// shutdown, this connection closed and the request recycled in
// a different thread. That thread will have updated the access
// log so it is OK not to update the access log here in that
// case.
// The other possibility is that an error occurred early in
// processing and the request could not be mapped to a Context.
// Log via the host or engine in that case.
long time = System.currentTimeMillis() - req.getStartTime();
if (context != null) {
context.logAccess(request, response, time, false);
} else if (response.isError()) {
if (host != null) {
host.logAccess(request, response, time, false);
} else {
connector.getService().getContainer().logAccess(
request, response, time, false);
}
}
}
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!async) {
updateWrapperErrorCount(request, response);
request.recycle();
response.recycle();
}
}
}
解析了Request中的一些信息後,並且解析成功則進入下面方法的調用:
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
此處便開始了 四個主要容器的執行流程 。
具體如何通過容器 一步步 進入SpringMVC 完成業務代碼的,下篇文章單獨講解吧。