Tomcat 的默认连接器

Tomcat 连接器是一个可以插入 servlet 容器的独立模块,已经存在相当多的连接器了,包 括 Coyote, mod_jk, mod_jk2 和 mod_webapp。一个 Tomcat 连接器必须符合以下条件:

  • 必须实现接口 org.apache.catalina.Connector。
  • 必须创建请求对象,该请求对象的类必须实现接口 org.apache.catalina.Request。
  • 必须创建响应对象,该响应对象的类必须实现接口 org.apache.catalina.Response。

Tomcat4的默认连接器类似于第3章的简单连接器。它等待前来的HTTP请求,创建request 和 response 对象,然后把 request 和 response 对象传递给容器。连接器是通过调用接口 org.apache.catalina.Container 的 invoke 方法来传递 request 和 response 对象的。invoke 的方法签名如下所示:

public void invoke(
  org.apache.catalina.Request request,
  org.apache.catalina.Response response
);

在 invoke 方法里边,容器加载 servlet,调用它的 service 方法,管理会话,记录出错日志等等。

HTTP 1.1 新特性

持久连接 

在 HTTP1.1 之前,无论什么时候浏览器连接到一个 web 服务器,当请求的资源被发送之后, 连接就被服务器关闭了。假如页面和它所引用到的全部资源使用不同连接来下载的话,进程将会非常慢。那就是为什么 HTTP1.1 引入持久连接的原因了。使用持久连接的时候,当页面下载的时候,服务器并不直接关闭连接。相反,它等待 web 客户端请求页面所引用的全部资源。这种情况下,页面和所引用的资源使用同一个连接来下载。考虑建立和解除 HTTP 连接的宝贵操作的话,这就为 web 服务器,客户端和网络节省了 许多工作和时间。

持久连接是 HTTP1.1 的默认连接方式。同样,为了明确这一点,浏览器可以发送一个值为 keep-alive 的请求头部 connection:
connection: keep-alive 

块编码 

HTTP/1.1 100 Continue 

Connector 接口

Tomcat 连接器必须实现 org.apache.catalina.Connector 接口。在这个接口的众多方法中, 最重要的是 getContainer,setContainer, createRequest 和 createResponse。

setContainer 是用来关联连接器和容器用的。getContainer 返回关联的容器。 createRequest 为前来的 HTTP 请求构造一个请求对象,而 createResponse 创建一个响应对象。 

维护 HttpProcessor 实例

在第 3 章中,HttpConnector 实例一次仅仅拥有一个 HttpProcessor 实例,所以每次只能处 理一个 HTTP 请求。在默认连接器 中,HttpConnector 拥有一个HttpProcessor 对象池,每个 HttpProcessor 实例拥有一个独立线程。因 此,HttpConnector 可以同时处理多个 HTTP 请求。 

为 HTTP 请求服务

就像第 3 章一样,HttpConnector 类在它的 run 方法中有其主要的逻辑。run 方法在一个服 务端套接字等待 HTTP 请求的地方存在一个 while 循环,一直运行直至 HttpConnector 被关闭了。

while (!stopped) {
    Socket socket = null;
    try {
        socket = serverSocket.accept();
        ...    

对每个前来的 HTTP 请求,会通过调用私有方法 createProcessor 获得一个 HttpProcessor实例

然而,大部分时候 createProcessor 方法并不创建一个新的 HttpProcessor 对象。相反,它从池子中获取一个。如果在栈中已经存在一 个 HttpProcessor 实例,createProcessor 将弹出 一个。如果栈是空的并且没有超过 HttpProcessor 实例的最大数 量,createProcessor 将会创 建一个。然而,如果已经达到最大数量的话,createProcessor 将会返回 null。出现这样的情况 的 话,套接字将会简单关闭并且前来的 HTTP 请求不会被处理。

HttpProcessor processor = createProcessor();
if (processor == null) {
    try {
        log(sm.getString("httpConnector.noProcessor"));
        socket.close();
    }
    ...
    continue;    

如果 createProcessor 不是返回 null,客户端套接字会传递给 HttpProcessor 类的 assign方法:

processor.assign(socket);

现在就是 HttpProcessor 实例用于读取套接字的输入流和解析 HTTP 请求的工作了。重要的 一点是,assign 方法不会等到 HttpProcessor 完成解析工作,而是必须马上返回,以便下一个

前来的 HTTP 请求可以被处理。每个 HttpProcessor 实例有自己的线程 用于解析,所以这点不是 很难做到。你将会在下节“HttpProcessor 类”中看到是怎么做的。 

 

HttpProcessor 类 

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 方法中的 while 循环按照这样的循序进行:获取一个套接字,处理它,调用连接器的recycle方法将当前HttpProcessor归还给对象池。 

需要注意的是,run中的while循环被await方法阻塞,直到HttpConnector调用HttpProcessor实例的assign方法。但是,await方法和assign方法运行在不同的线程上。assign方法从HttpConnector的run方法中调用。我们就说这个线程是HttpConnector实例的run方法运行的处理线程。assign方法是如何通知已经被调用的await方法的?就是通过一个布尔变量 available并且使用Object的wait和notifyAll方法。 

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);

    }

刚开始的时候,当处理器线程刚启动的时候,available 为false,线程在 while 循环里边等待。

当一个新的套接字被分配的时候,连接器线程调用 HttpProcessor 的 assign 方法。available 的值是 false,所以 while 循环给跳过,并且套接字给赋值给HttpProcessor实例的socket变量,调用notifyAll,最后返回并处理该socket

 
posted on 2016-05-06 22:05  cbwleft  阅读(351)  评论(0)    收藏  举报