天天看點

tomcat源碼分析-Connector初始化與啟動

   一個應用應用伺服器的性能很大程度上取決于網絡通信子產品的實作,因而connector子產品對于tomcat來說是重中之重。從tomcat5開始,預設的連接配接器實作為coyote實作(orag.apache.tomcat:tomcat-coyote:7.0.57),本文基于coyote實作會回答如下兩個問題:

    一個http請求是怎麼被tomcat監聽到的,會有那些處理;

    ajp協定幹什麼用的。

一、connector配置

tomcat源碼分析-Connector初始化與啟動

<connector port="8080" protocol="http/1.1"  

               connectiontimeout="20000"  

               redirectport="8443" />  

<connector port="8009" protocol="ajp/1.3" redirectport="8443" />  

為什麼會有多個connector呢?我們部署伺服器的時候,通常會有2種方式:

    1 直接部署tomcat,在浏覽器中請求http與tomcat直連

    2 部署一個nginx作反向代理,tomcat與nginx直連

    好的,現在配置了2個connector,那麼繼續思考一下,connector是通信過程,如果是你你會怎麼設計?顯然需要做3件事:

(1)監聽端口,建立服務端與用戶端的連結;

(2)擷取到用戶端請求的socket資料,并對socket資料進行解析和包裝成http請求資料格式;

(3)将包裝後的資料交給container處理

通過源碼來分析,connector有兩個屬性:protocolhandler(協定)和adapter(擴充卡),其中protocolhandler完成的是步驟(1)(2),adapter完成的是步驟(3)。

二、connector初始化

    1. connector構造函數

    在connector的構造方法中,通過反射生成protocolhandler。

tomcat源碼分析-Connector初始化與啟動

public connector(string protocol) {  

    setprotocol(protocol);  

    // instantiate protocol handler  

    try {  

        class<?> clazz = class.forname(protocolhandlerclassname);  

        this.protocolhandler = (protocolhandler) clazz.newinstance();  

    } catch (exception e) {  

        log.error(sm.getstring(  

                "coyoteconnector.protocolhandlerinstantiationfailed"), e);  

    }  

}  

public void setprotocol(string protocol) {  

        if (aprlifecyclelistener.isapravailable()) {  

            if ("http/1.1".equals(protocol)) {  

                setprotocolhandlerclassname  

                    ("org.apache.coyote.http11.http11aprprotocol");  

            } else if ("ajp/1.3".equals(protocol)) {  

                    ("org.apache.coyote.ajp.ajpaprprotocol");  

            } else if (protocol != null) {  

                setprotocolhandlerclassname(protocol);  

            } else {  

            }  

        } else {  

                    ("org.apache.coyote.http11.http11protocol");  

                    ("org.apache.coyote.ajp.ajpprotocol");  

        }  

 協定的設定在conf/server.xml中配置,由setprotocol來指派,tomcat提供了6種協定:

tomcat源碼分析-Connector初始化與啟動

由上面6個類可知,對于http/ajp協定,tomcat均提供了三種運作模式,及bio、nio、apr,bio即傳統的blocking io,性能是最差的,apr是通過安裝apr和native,從作業系統層面解決異步io問題,能大幅提高性能,最省心也能較大幅度提高性能的是nio,隻需将connector的protocol配成"org.apache.coyote.http11.http11nioprotocol"即可。

    為了便于分析,這裡隻分析http11protocol。由類圖可以看到,歸根結底http11protocol是protocolhandler的實作,在http11protocol的構造方法中,對成員變量endpoint和chandler進行初始化

tomcat源碼分析-Connector初始化與啟動

public http11protocol() {  

        endpoint = new jioendpoint();  

        chandler = new http11connectionhandler(this);  

        ((jioendpoint) endpoint).sethandler(chandler);  

        setsolinger(constants.default_connection_linger);  

        setsotimeout(constants.default_connection_timeout);  

        settcpnodelay(constants.default_tcp_no_delay);  

 ,這兩個很重要,在後面會講到。

    繼續到connector代碼中,由前面提到的tomcat啟動過程知道,會調用connector兩個方法init和start。而端口的綁定和監聽則分别在這兩個方法中完成。

    2. connector的init()方法

    調用的是connector的initinternal()方法,主要做了3件事

tomcat源碼分析-Connector初始化與啟動

protected void initinternal() throws lifecycleexception {  

        super.initinternal();  

        // step 1. initialize adapter  

        adapter = new coyoteadapter(this);  

        protocolhandler.setadapter(adapter);  

        // make sure parsebodymethodsset has a default  

        if( null == parsebodymethodsset ) {  

            setparsebodymethods(getparsebodymethods());  

        if (protocolhandler.isaprrequired() &&  

                !aprlifecyclelistener.isapravailable()) {  

            //  

        try {  

            // step 2  

            protocolhandler.init();  

        } catch (exception e) {  

        // step 3 initialize mapper listener  

        mapperlistener.init();  

步驟2中http11protocol的init方法,最終會調用到其父類abstractprotocol的init方法,在這個方法裡面對endpoint(http11protocol使用的是jioendpoint)進行了初始化。

tomcat源碼分析-Connector初始化與啟動

public void init() throws exception {  

        // ...  

        string endpointname = getname();  

        endpoint.setname(endpointname.substring(1, endpointname.length()-1));  

            endpoint.init();  

        } catch (exception ex) {  

 endpoint.init()在abstractendpoint中,完成了對需要監聽的端口的綁定。

tomcat源碼分析-Connector初始化與啟動

public final void init() throws exception {  

        if (bindoninit) {  

            bind();  

            bindstate = bindstate.bound_on_init;  

 在jioendpoint的bind()中完成了對端口的綁定。

對于http11nioprotocol,在nioendpoint的bind()方法中我們發現了selector,

tomcat源碼分析-Connector初始化與啟動

@override  

    public void bind() throws exception {  

        serversock = serversocketchannel.open();  

        socketproperties.setproperties(serversock.socket());  

        inetsocketaddress addr = (getaddress()!=null?new inetsocketaddress(getaddress(),getport()):new inetsocketaddress(getport()));  

        serversock.socket().bind(addr,getbacklog());  

        serversock.configureblocking(true); //mimic apr behavior  

        serversock.socket().setsotimeout(getsocketproperties().getsotimeout());  

        // initialize thread count defaults for acceptor, poller  

        if (acceptorthreadcount == 0) {  

            // fixme: doesn't seem to work that well with multiple accept threads  

            acceptorthreadcount = 1;  

        if (pollerthreadcount <= 0) {  

            //minimum one poller thread  

            pollerthreadcount = 1;  

        stoplatch = new countdownlatch(pollerthreadcount);  

        //...  

        selectorpool.open();  

 而讓我們震驚的是,coyote用了自己搞的一個nioselectorpool,其中maxselectors=200與普通io下acceptor的數量一樣。另外,普通io下用的處理請求的線程池的核心線程數量是10,max是200.當然,這些值可在配置檔案裡配。

tomcat源碼分析-Connector初始化與啟動

public class nioselectorpool {  

    public nioselectorpool() {  

    private static final log log = logfactory.getlog(nioselectorpool.class);  

    protected static final boolean shared =  

        boolean.valueof(system.getproperty("org.apache.tomcat.util.net.nioselectorshared", "true")).booleanvalue();  

    protected nioblockingselector blockingselector;  

    protected volatile selector shared_selector;  

    protected int maxselectors = 200;  

    protected long sharedselectortimeout = 30000;  

    protected int maxspareselectors = -1;  

    protected boolean enabled = true;  

    protected atomicinteger active = new atomicinteger(0);  

    protected atomicinteger spare = new atomicinteger(0);  

    protected concurrentlinkedqueue<selector> selectors =  

        new concurrentlinkedqueue<selector>();  

    //...  

三、connector啟動

    connector的啟動會調用start方法,在startinternal方法中,

tomcat源碼分析-Connector初始化與啟動

protected void startinternal() throws lifecycleexception {  

        // validate settings before starting  

        if (getport() < 0) {  

        setstate(lifecyclestate.starting); // 發送starting事件  

            protocolhandler.start(); // 啟動端口監聽  

        mapperlistener.start(); // 這個很重要,後面會講到  

 可以看到,start相對init是調用了對應的start方法。其中,protocolhandler.start();即調用了http11protocol的start方法。最終調用了調用了jioendpoint的startinternal方法,初始化了處理連接配接請求的線程池(預設最大線程數200個),開啟acceptor線程接收請求。

tomcat源碼分析-Connector初始化與啟動

public void startinternal() throws exception {  

        if (!running) {  

            running = true;  

            paused = false;  

            // create worker collection  

            if (getexecutor() == null) {  

                createexecutor();  

            initializeconnectionlatch();  

            startacceptorthreads();  

            // start async timeout thread  

            thread timeoutthread = new thread(new asynctimeout(),  

                    getname() + "-asynctimeout");  

            timeoutthread.setpriority(threadpriority);  

            timeoutthread.setdaemon(true);  

            timeoutthread.start();  

四、一個http請求在connector中所經曆的代碼邏輯

    先來個粗犷的印象,

tomcat源碼分析-Connector初始化與啟動

lifecyclebase implements lifecycle  

start() lifecycle  

  startinternal() lifecyclebase  

------------------------------  

catalina#load()  

  standardserver#init()  

    standservice[i]#init()  

      container[i]#init()  

      executor[i]#init()  

      connector[i]#init()  

connector#initinternal()  

  adapter = new coyoteadapter(this)  

  abstractprotocol extends protocolhandler.setadapter(adapter)  

  protocolhandler.init()  

  mapperlistener.init()  

abstractprotocol implements protocolhandler#init()  

  abstractendpoint#init()  

    bind()  

catalina#start()  

  if (getserver() == null) load();  

  standardserver#start()  

    standardservice[i]#start()  

      container#start()  

      executor[i]#start()  

      connector[i]#start()  

connector#startinternal()  

  abstractprotocol implements protocolhandler#start()  

    abstractendpoint#start()  

  mapperlistener.start()  

jioendpoint#startinternal()  

  createexecutor()  

  initializeconnectionlatch()  

  startacceptorthreads()  

abstractendpoint#startacceptorthreads()  

  acceptors[i] = createacceptor()  

  new thread(acceptors[i]).start()  

jioendpoint.acceptor implements runnable  

  socket socket = serversocketfactory.acceptsocket(serversocket)  

  processsocket(socket)  

    socketwrapper<socket> wrapper = new socketwrapper<socket>(socket)  

    getexecutor().execute(new socketprocessor(wrapper))  

socketprocessor implements runnable  

  state = handler.process(socket,status);  

http11protocol ... extends abstractprotocol<s>  

   http11connectionhandler extends abstractconnectionhandler implements handler  

abstractprotocol  

   abstractconnectionhandler implements abstractendpoint.handler  

      socketstate process(socketwrapper<s> wrapper, socketstatus status)  

        state = processor.process(wrapper)  

abstracthttp11processor  

  socketstate process(socketwrapper<s> socketwrapper)  

    adapter.service(request, response)  

 在上文中有分析connector在啟動的時候會監聽端口。繼續以jioendpoint為例,在其accptor類中:

tomcat源碼分析-Connector初始化與啟動

protected class acceptor extends abstractendpoint.acceptor {  

    @override  

    public void run() {  

        while (running) {  

            try {  

                //目前連接配接數  

                countuporawaitconnection();  

                socket socket = null;  

                try {  

                    //取出隊列中的連接配接請求  

                    socket = serversocketfactory.acceptsocket(serversocket);  

                } catch (ioexception ioe) {  

                    countdownconnection();  

                }  

                if (running && !paused && setsocketoptions(socket)) {  

                    //處理請求  

                    if (!processsocket(socket)) {  

                        countdownconnection();  

                        closesocket(socket);  

                    }  

                } else {  

                    // close socket right away  

                    closesocket(socket);  

            }   

 在上面的代碼中,socket = serversocketfactory.acceptsocket(serversocket);與用戶端建立連接配接,将連接配接的socket交給processsocket(socket)來處理。在processsocket中,對socket進行包裝一下交給線程池來處理:

tomcat源碼分析-Connector初始化與啟動

protected boolean processsocket(socket socket) {  

        socketwrapper<socket> wrapper = new socketwrapper<socket>(socket);  

        wrapper.setkeepaliveleft(getmaxkeepaliverequests());  

        wrapper.setsecure(issslenabled());  

        //交給線程池處理連接配接  

        getexecutor().execute(new socketprocessor(wrapper));  

    }   

    //  

    return true;  

 線程池處理的任務socketproccessor,通過代碼分析:

tomcat源碼分析-Connector初始化與啟動

protected class socketprocessor implements runnable {  

    protected socketwrapper<socket> socket = null;  

    protected socketstatus status = null;  

        boolean launch = false;  

        synchronized (socket) {  

            socketstate state = socketstate.open;  

                serversocketfactory.handshake(socket.getsocket());  

            if ((state != socketstate.closed)) {  

                //委派給handler來處理  

                if (status == null) {  

                    state = handler.process(socket, socketstatus.open_read);  

                    state = handler.process(socket,status);  

            }}}  

 即在socketprocessor中,将socket交給handler處理,這個handler就是在http11protocol的構造方法中指派的http11connectionhandler,在該類的父類process方法中通過請求的狀态,來建立http11processor處理器進行相應的處理,切到http11proccessor的父類abstracthttp11proccessor中。

tomcat源碼分析-Connector初始化與啟動

public socketstate process(socketwrapper socketwrapper) {  

    requestinfo rp = request.getrequestprocessor();  

    rp.setstage(org.apache.coyote.constants.stage_parse);  

    // setting up the i/o  

    setsocketwrapper(socketwrapper);  

    getinputbuffer().init(socketwrapper, endpoint);  

    getoutputbuffer().init(socketwrapper, endpoint);  

    while (!geterrorstate().iserror() && keepalive && !comet && !isasync() &&  

            upgradeinbound == null &&  

            httpupgradehandler == null && !endpoint.ispaused()) {  

        //  

        if (!geterrorstate().iserror()) {  

            // setting up filters, and parse some request headers  

            rp.setstage(org.apache.coyote.constants.stage_prepare);  

                //請求預處理  

                preparerequest();  

                rp.setstage(org.apache.coyote.constants.stage_service);  

                //交由擴充卡處理  

                adapter.service(request, response);  

                if(keepalive && !geterrorstate().iserror() && (  

                        response.geterrorexception() != null ||  

                                (!isasync() &&  

                                statusdropsconnection(response.getstatus())))) {  

                    seterrorstate(errorstate.close_clean, null);  

                setcomettimeouts(socketwrapper);  

}            

代碼很長,删掉的部分比較多,在這個方法裡,可以看到request和response的生成,從socket中擷取請求資料,keep-alive處理,資料包裝等,最後交給了coyoteadapter的service方法。

原文連結:[http://wely.iteye.com/blog/2295171]