Tomcat源碼之Connector(1)

參考博客:
Tomcat源碼之Connector (1)

一、 Connector介紹

1. Connector

監聽端口,接受客戶端請求並轉交給Engine處理,同時將答覆轉發給客戶端。

2. Connector種類

與Connector相關的類都在org.apache.coyote包中,分爲幾類:

  1. Http Connector,基於HTTP協議,負責建立HTTP連接。它又分爲BIO Http Connector與NIO Http Connector兩種,後者提供非阻塞IO與長連接Comet支持。默認情況下,Tomcat使用的就是這個Connector。
  2. AJP Connector, 基於AJP協議,AJP是專門設計用來爲tomcat與http服務器之間通信專門定製的協議,能提供較高的通信速度和效率。如與Apache服務器集成時,採用這個協議。
  3. APR HTTP Connector, 用C實現,通過JNI調用的。主要提升對靜態資源(如HTML、圖片、CSS、js等)的訪問性能。現在這個庫已獨立出來可用在任何項目中。Tomcat在配置APR之後性能非常強勁。

具體地,Tomcat7中實現了以下幾種Connector:

  • org.apache.coyote.http11.Http11Protocol : 支持HTTP/1.1 協議的連接器。
  • org.apache.coyote.http11.Http11NioProtocol : 支持HTTP/1.1 協議+New IO的連接器。
  • org.apache.coyote.http11.Http11AprProtocol : 使用APR(Apache portable runtime)技術的連接器,利用Native代碼與本地服務器(如Linux)來提高性能。

以上三種Connector實現都是直接處理來自客戶端Http請求,加上NIO或者APR

  • org.apache.coyote.ajp.AjpProtocol:使用AJP協議的連接器,實現與web server(如Apache httpd)之間的通信
  • org.apache.coyote.ajp.AjpNioProtocol:SJP協議+ New IO
  • org.apache.coyote.ajp.AjpAprProtocol:AJP + APR

以上三種實現方法則是與web server打交道,同樣加上NIO和APR

我們可以通過實現ProtocolHandler接口來定義自己的Connector。

3. Connector的配置

對Connector的配置位於conf/server.xml文件中,內嵌在Service元素中,可以有多個Connector元素。

1) BIO HTTP/1.1 Connector配置

一個典型的配置如下:

<Connector  port=”8080”  protocol=”HTTP/1.1maxThreads=”150”  connectionTimeout=”20000”   redirectPort=”8443” />

其它一些重要屬性如下:
acceptCount : 接受連接request的最大連接數目,默認值是10
address : 綁定IP地址,如果不綁定,默認將綁定任何IP地址
allowTrace : 如果是true,將允許TRACE HTTP方法
compressibleMimeTypes : 各個mimeType, 以逗號分隔,如text/html,text/xml
compression : 如果帶寬有限的話,可以用GZIP壓縮
connectionTimeout : 超時時間,默認爲60000ms (60s)
maxKeepAliveRequest : 默認值是100
maxThreads : 處理請求的Connector的線程數目,默認值爲200

如果是SSL配置,如下:

<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true" 
    maxThreads="150" scheme="https" secure="true" 
    clientAuth="false" sslProtocol = "TLS" 
    address="0.0.0.0" 
    keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks" 
    keystorePass="changeit" />

其中,keystoreFile爲證書位置,keystorePass爲證書密碼.

2) NIO HTTP/1.1 Connector配置

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol” maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”/>

3)Native APR Connector配置

ARP是用C/C++寫的,對靜態資源(HTML,圖片等)進行了優化。所以要下載本地庫tcnative-1.dll與openssl.exe,將其放在%tomcat%\bin目錄下。
下載地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/

在server.xml中要配置一個Listener,如下圖。這個配置tomcat是默認配好的。

<!--APR library loader. Documentation at /docs/apr.html --> 
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

配置使用APR connector

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol” 
maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”

如果配置成功,啓動tomcat,會看到一下信息:

org.apache.coyote.http11.Http11AprProtocol init

4) AJP Connector配置

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

二、Connector在Tomcat中處的位置

1. Tomcat架構

Server(服務器)是Tomcat構成的頂級構成元素,所有一切均包含在Server中,Server的實現類StandardServer可以包含一個到多個Services;
Service的實現類爲StandardService調用了容器(Container)接口,其實是調用了Servlet Engine(引擎),而且StandardService類中也指明瞭該Service歸屬的Server

Container,引擎(Engine)、主機(Host) 上下文(Context)和Wraper均繼承自Container接口,所以它們都是容器。但是,它們是有父子關係的,在主機(Host)、上下文(Context)和引擎(Engine)這三類容器中,引擎是頂級容器,直接包含是主機容器,而主機容器又包含上下文容器,所以引擎、主機和上下文從大小上來說又構成父子關係,雖然它們都繼承自Container接口。

連接器(Connector)將Service和Container連接起來,首先它需要註冊到一個Service,它的作用就是把來自客戶端的請求轉發到Container(容器),這就是它爲什麼稱作連接器的原因。

故我們從功能的角度將Tomcat源代碼分成5個子模塊,它們分別是:

  1. Jsper子模塊:這個子模塊負責jsp頁面的解析、jsp屬性的驗證,同時也負責將jsp頁面動態轉換爲Java代碼並編譯成class文件。在Tomcat源代碼中,凡是屬於org.apache.jasper包及其子包中的源代碼都屬於這個子模塊;
  2. Servlet和Jsp規範的實現模塊:這個子模塊的源代碼屬於javax.servlet包及其子包,如我們非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet類及javax.servlet.jsp.HttpJspPage就位於這個子模塊中;
  3. Catalina子模塊:這個子模塊包含了所有以org.apache.catalina開頭的java源代碼。該子模塊的任務是規範了Tomcat的總體架構,定義了Server、Service、Host、Connector、Context、Session及Cluster等關鍵組件及這些組件的實現,這個子模塊大量運用了Composite設計模式。同時也規範了Catalina的啓動及停止等事件的執行流程。從代碼閱讀的角度看,這個子模塊應該是我們閱讀和學習的重點。
  4. Connector子模塊:如果說上面三個子模塊實現了Tomcat應用服務器的話,那麼這個子模塊就是Web服務器的實現。所謂連接器(Connector)就是一個連接客戶和應用服務器的橋樑,它接收用戶的請求,並把用戶請求包裝成標準的Http請求(包含協議名稱,請求頭Head,請求方法是Get還是Post等等)。同時,這個子模塊還按照標準的Http協議,負責給客戶端發送響應頁面,比如在請求頁面未發現時,connector就會給客戶端瀏覽器發送標準的Http 404錯誤響應頁面。
  5. Resource子模塊:這個子模塊包含一些資源文件,如Server.xml及Web.xml配置文件。嚴格說來,這個子模塊不包含java源代碼,但是它還是Tomcat編譯運行所必需的。

2. Tomcat運行流程

假設來自客戶的請求爲:http://localhost:8080/test/index.jsp 請求被髮送到本機端口8080

  1. 被在那裏偵聽的Coyote HTTP/1.1 Connector獲得;
  2. 然後Connector把該請求交給它所在的Service的Engine來處理,並等待Engine的迴應;
  3. Engine獲得請求localhost:8080/test/index.jsp,匹配它所有虛擬主機Host;
  4. Engine匹配到名爲localhost的Host(即使匹配不到也把請求交給該Host處理,因爲該Host被定義爲該Engine的默認主機);
  5. localhost Host獲得請求/test/index.jsp,匹配它所擁有的所有Context;
  6. Host匹配到路徑爲/test的Context(如果匹配不到就把該請求交給路徑名爲”“的Context去處理);
  7. path=”/test”的Context獲得請求/index.jsp,在它的mapping table中尋找對應的servlet;
  8. Context匹配到URL PATTERN爲*.jsp的servlet,對應於JspServlet類,構造HttpServletRequest對象和HttpServletResponse對象,作爲參數調用JspServlet的doGet或doPost方法;
  9. Context把執行完了之後的HttpServletResponse對象返回給Host;
  10. Host把HttpServletResponse對象返回給Engine;
  11. Engine把HttpServletResponse對象返回給Connector;
  12. Connector把HttpServletResponse對象返回給客戶browser。

3. Tomcat源碼分析

1. Tomcat的啓動分析與集成設想

我們知道,啓動tomcat有兩種方式:

雙擊bin/startup.bat
運行bin/catalina.bat run

它們對應於Bootstrap與Catalina兩個類,我們現在只關心Catalina這個類,這個類使用Apache Digester解析conf/server.xml文件生成tomcat組件,然後再調用Embedded類的start方法啓動tomcat。

所以,集成Tomcat的方式就有以下兩種了:

  • ①沿用tomcat自身的server.xml
  • ②自己定義一個xml格式來配置tocmat的各參數,自己再寫解析這段xml,然後使用tomcat提供的API根據這些xml來生成Tomcat組件,最後調用Embedded類的start方法啓動tomcat

個人覺得第一種方式要優越,給開發者比較好的用戶體驗,如果使用這種,直接模仿Catalina類的方法即可實現集成。

目前,JOnAS就使用了這種集成方式,JBoss、GlassFish使用的第二種自定義XML的方式。

2. Connector類圖與順序圖

從上面二圖中我們可以得到如下信息:

Tomcat中有四種容器(Context、Engine、Host、Wrapper),前三者常見,第四個不常見但它也是實現了Container接口的容器

如果要自定義一個Connector的話,只需要實現org.apache.coyote.ProtocolHander接口,該接口定義如下:

<span style="font-size:14px;">/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.coyote;

import java.util.concurrent.Executor;


/**
 * Abstract the protocol implementation, including threading, etc.
 * Processor is single threaded and specific to stream-based protocols,
 * will not fit Jk protocols like JNI.
 *
 * This is the main interface to be implemented by a coyote connector.
 * Adapter is the main interface to be implemented by a coyote servlet
 * container.
 *
 * @author Remy Maucherat
 * @author Costin Manolache
 * @see Adapter
 */
public interface ProtocolHandler {   //Tomcat 中的Connector實現了這個接口

    /**
     * The adapter, used to call the connector.
     */
    public void setAdapter(Adapter adapter);
    public Adapter getAdapter();


    /**
     * The executor, provide access to the underlying thread pool.
     */
    public Executor getExecutor();


    /**
     * Initialise the protocol.
     */
    public void init() throws Exception;


    /**
     * Start the protocol.
     */
    public void start() throws Exception;


    /**
     * Pause the protocol (optional).
     */
    public void pause() throws Exception;


    /**
     * Resume the protocol (optional).
     */
    public void resume() throws Exception;


    /**
     * Stop the protocol.
     */
    public void stop() throws Exception;


    /**
     * Destroy the protocol (optional).
     */
    public void destroy() throws Exception;


    /**
     * Requires APR/native library
     */
    public boolean isAprRequired();
}
</span>

自定義Connector時需實現的ProtoclHandler接口

Tomcat以HTTP(包括BIO與NIO)、AJP、APR、內存四種協議實現了該接口(它們分別是:AjpAprProtocol、AjpProtocol、Http11AprProtocol、Http11NioProtocol、Http11Protocal、JkCoyoteHandler、MemoryProtocolHandler),要使用哪種Connector就在conf/server.xml中配置,在Connector的構造函數中會通過反射實例化所配置的實現類:

<Connector port="8181" 
   protocol="org.apache.coyote.http11.Http11AprProtocol " />

3. Connector工作原理

下面我們以org.apache.coyote.http11.Http11AprProtocol爲例說明Connector的工作流程。

  1. 它將工作委託給AprEndpoint類

    public Http11AprProtocol() {
        endpoint = new AprEndpoint();                    // 主要工作由AprEndpoint來完成
        cHandler = new Http11ConnectionHandler(this);    // inner class
        ((AprEndpoint) endpoint).setHandler(cHandler);
        }
  2. 在AprEndpoint.Acceptor類中的run()方法會接收一個客戶端新的連接請求.

    /**
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    protected class Acceptor extends AbstractEndpoint.Acceptor {
    
        private final Log log = LogFactory.getLog(AprEndpoint.Acceptor.class);
    
        @Override
        public void run() {
    
            int errorDelay = 0;
    
            // Loop until we receive a shutdown command
            while (running) {
    
    1. 在AprEndpoint類中,有一個內部接口Handler,該接口定義如下:

    “`
    public interface Handler extends AbstractEndpoint.Handler {
    public SocketState process(SocketWrapper socket,
    SocketStatus status);
    }


4. 在Http11AprProtocol類中實現了AprEndpoint中的Handler接口,

protected static class Http11ConnectionHandler
extends AbstractConnectionHandler


#### 4. 通過Connector實現一個簡單的Server

通過以下這些步驟,你就可以建立一個很簡單的服務器,除了用到tomcat的一些類之外,跟Tomcat沒有任何關係。

首先,建立一個含有main()方法的java類,詳細代碼如下:

package myConnector;

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;

/**
* 基於Http11Protocol實現一個簡單的服務器
* @author bingduanLin
*
*/
public class MyServer {

/**
 * @param args
 */
public static void main(String[] args) throws Exception{
    Http11Protocol hp = new Http11Protocol();
    hp.setPort(9000);
    ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();
    threadPoolExecutor.prestartCoreThread();
    hp.setExecutor(threadPoolExecutor);
    hp.setAdapter(new myHandler());
    hp.init();
    hp.start();
    System.out.println("My Server has started successfully!");


}

public static ThreadPoolExecutor createThreadPoolExecutor() {
    int corePoolSite = 2 ;
    int maxPoolSite = 10 ;
    long keepAliveTime = 60 ;
    TimeUnit unit = TimeUnit.SECONDS;
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
    ThreadPoolExecutor threadPoolExecutor = 
            new ThreadPoolExecutor(corePoolSite, maxPoolSite,
                    keepAliveTime, unit, workQueue);
    return threadPoolExecutor;
}

}


上面這個類用到的MyHandler類代碼如下:

package TomcatTest;

/**
* Created by yunzhong.wangyunzh on 2017/7/31.
*/
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 {

public void service(Request req, Response res) throws Exception {
    // 請求處理
    System.out.println("Hi, Boss. I am handling the reuqest!");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));
    writer.println("Not Hello World");
    writer.flush();

    ByteChunk byteChunk = new ByteChunk();
    byteChunk.append(baos.toByteArray(), 0, baos.size());
    res.doWrite(byteChunk);

}


public boolean event(Request req, Response res, SocketStatus status)
        throws Exception {
    System.out.println("Event-Event");
    return false;
}


public boolean asyncDispatch(Request req, Response res, SocketStatus status)
        throws Exception {
    // TODO Auto-generated method stub
    return false;
}


public void log(Request req, Response res, long time) {
    // TODO Auto-generated method stub

}


public String getDomain() {
    // TODO Auto-generated method stub
    return null;
}

public void errorDispatch(Request request, Response response) {

}

public void checkRecycled(Request request, Response response) {

}

}

注意,利用tomcat 7相關的依賴包,建議使用IDEA創建javamaven項目。

完成之後運行主程序即:MyServer。然後在瀏覽器中輸入:localhost:9000 就可以看到“Not Hello World”。

下面是主程序的一些輸出:

十二月 20, 2012 8:46:12 下午 org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler [“http-bio-9000”]
十二月 20, 2012 8:46:13 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler [“http-bio-9000”]
My Server has started successfully!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
“`

發佈了48 篇原創文章 · 獲贊 11 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章