Tomcat源码分析(二)--连接处理

目标:在这篇文章希望搞明白http请求到tomcat后是怎么由连接器转交到容器的?

在上一节里已经启动了一个HttpConnector线程,并且也启动了固定数量的HttpProcessor线程。HttpConnector用来等待http连接,得到http连接后交给其中的一个HttpProcessor线程来处理。接下里具体看一下HttpConnector是怎么得到连接得,以及HttpProcessor是怎么处理的。当启动了HttpConnector线程后(在上一节已经知道怎么启动了),便在它的run方法里面循环等待:

public void run() {
        // Loop until we receive a shutdown command
        while (!stopped) {
            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                socket = serverSocket.accept();
                if (connectionTimeout > 0)
                    socket.setSoTimeout(connectionTimeout);
                socket.setTcpNoDelay(tcpNoDelay);
            } catch (AccessControlException ace) {
                log("socket accept security exception", ace);
                continue;
            } catch (IOException e) {
                try {
                    // If reopening fails, exit
                    synchronized (threadSync) {
                        if (started && !stopped)
                            log("accept error: ", e);
                        if (!stopped) {
                            serverSocket.close();
                            serverSocket = open();
                        }
                    }
                    
                } catch (IOException ioe) {
                    log("socket reopen, io problem: ", ioe);
                    break;
                } catch (KeyStoreException kse) {
                    log("socket reopen, keystore problem: ", kse);
                    break;
                } catch (NoSuchAlgorithmException nsae) {
                    log("socket reopen, keystore algorithm problem: ", nsae);
                    break;
                } catch (CertificateException ce) {
                    log("socket reopen, certificate problem: ", ce);
                    break;
                } catch (UnrecoverableKeyException uke) {
                    log("socket reopen, unrecoverable key: ", uke);
                    break;
                } catch (KeyManagementException kme) {
                    log("socket reopen, key management problem: ", kme);
                    break;
                }

                continue;
            }

            // Hand this socket off to an appropriate processor
            HttpProcessor processor = createProcessor();
            if (processor == null) {
                try {
                    log(sm.getString("httpConnector.noProcessor"));
                    socket.close();
                } catch (IOException e) {
                    ;
                }
                continue;
            }
         
            processor.assign(socket);

        }

        // Notify the threadStop() method that we have shut ourselves down
     
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

这里很关键的就是socket = serverSocket.accept();和processor.assign(socket); 在循环里面内,serverSocket.accept();负责接收http请求然后赋值给socket,最后交给其中一个processor处理。这里processor并不是等到需要的时候再实例化,而是在HttpConnector初始化的时候已经有了若干个processor,在httpConnector里有这样一个声明:

private Stack processors = new Stack();

表明httpConnector里面持有一个包含HttpProcessor对象的栈,需要的时候拿出来就是了。看一下createProcessor函数就能比较明白了:

private HttpProcessor createProcessor() {
        synchronized (processors) {
            if (processors.size() > 0) {
                return ((HttpProcessor) processors.pop()); //从processors栈中弹出一个processor
            }
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }

    }

接下来由processor.assign(socket); 记住这个方法是异步的,不需要等待HttpProcessor来处理完成,所以HttpConnector才能不间断的传入Http请求,在HttpProcessor里有两个方法比较重要,这两个方法协调处理了由HttpConnector传来的socket:

synchronized void assign(Socket socket) {

        // Wait for the Processor to get the previous Socket
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log(" An incoming request is being assigned");

    }


    private synchronized Socket await() {

        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log("  The incoming request has been awaited");

        return (socket);

    }

看一下HttpProcessor的run方法:

public void run() {

        // Process requests until we receive a shutdown signal
        while (!stopped) {

            // Wait for the next socket to be assigned
            Socket socket = await();
            if (socket == null)
                continue;

            // Process the request from this socket
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }

            // Finish up this request
            connector.recycle(this);

        }

        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

很明显,在它的run方法一开始便是调用上面的await方法来等待(因为一开始available变量为false),所以HttpProcessor会一直阻塞,直到有线程来唤醒它。当从HttpConnector中调用processor.assign(socket),会把socket传给此HttpProcessor对象,并设置available为true,调用notifyAll()唤醒该processor线程以处理socket。同时,在await方法中又把available设置成false,因此又回到初始状态,即可以重新接受socket。

这里处理socket的方法是process(socket),主要作用有两点,1:解析这个socket,即解析http请求,包括请求方法,请求协议等,以填充request,response对象(是不是很熟悉,在servlet和jsp开发经常用到的request,response对象就是从这里来的)。2:传入request,response对象给和HttpConnector绑定的容器,让容器来调用invoke方法进行处理。process方法主要的代码如下:

private void process(Socket socket) {
                   input = new SocketInputStream(socket.getInputStream(),
                                          connector.getBufferSize());
                   //解析一下连接的地址,端口什么的
                    parseConnection(socket);
                    //解析请求头的第一行,即:方法,协议,uri
                    parseRequest(input, output);
                    if (!request.getRequest().getProtocol()
                        .startsWith("HTTP/0"))
                    parseHeaders(input);//解析http协议的头部
                    ..............................................
                    connector.getContainer().invoke(request, response);
                    .............................................
}

在那些parse××方法里面会对request,response对象进行初始化,然后调用容器的invoke方法进行处理,至此,http请求过来的连接已经完美的转交给容器处理,容器剩下的问题就是要最终转交给哪个servlet或者jsp的问题。前面我们知道,一个连接会跟一个容器相连,一个级别大的容器会有一个或者多个子容器,最小的容器是Wrapper,对应一个servlet,在这里我们只要知道请求的路径决定了最终会选择哪个wrapper,wrapper最终会调用servlet的。至少一开始提出来的问题已经明白了。这里又有一个问题,在调用invoke方法是有这样的connector.getContainer的代码,即通过连接器得到跟它关联的容器,这个连接器是什么时候跟容器关联上的?详见下篇:Tomcat源码分析(三)--连接器是如何与容器关联的?

相关推荐