tomcat源码阅读-2

这是阅读《how tomcat works》第四章后的总结。


本章主要讲的是tomcat4的defaultConnector(即HttpConnector,但是为了区别于第三章中的HttpConnector类,本文中称之为defaultConnector,也是因为他是tomcat4的默认connector。该类已经被遗弃了,由Coyote替代),文中提到connector是一个独立的模块,可以被其他实现所替换,已知的可用来替换的有:Coyote,mod_jk, mod_jk2,和 mod_webapp。其实想自定义一个connector类来替换defaultConnector很简单,该类只要满足一下三个提交即可:

  1. 实现org.apache.catalina.Connector接口
  2. 创建实现了org.apache.catalina.Reques接口的request对象
  3. 创建实现了org.apache.catalina.Response接口的response对象

其实defaultConnector和第三章中的connector工作原理非常相似,在我看来唯一不同的是defaultConnector类采用了多线程的实现方式(当然还实现了文章中提到的http1.1的新特性)。

我们首先回过头来看第三章的connector——HttpConnector类,该类实现了Runnable接口,在run方法中创建ServerSocket对象阻塞接受请求,当接收到请求后,会创建一个HttpProcessor对象并调用其process方法处理请求,但是这一步也是阻塞的(即同步的),只有当process方法完全处理完请求后,HttpConnector才能接受下一个请求。由此可见,效率是多么的底下啊。

经过简单的ab压力测试,结果如下:

环境:

cpu:酷睿I5

内存:4G

total request:1000

concurrent:10



而第四章中的defaultConnnector则不同,不同之处在于defaultConnector在处理request的时候采用了线程池。具体过程如下:

  1. 首先defaultConnector在初始化后会立即创建一个HttpProcessor pool
  2. 然后接收到一个请求后会从pool中获取一个HttpProcessor实例,如果没有则创建一个新的Httpprocessor实例,超过maxProcessors规定的数量则直接忽略该请求(如果不想限制processor数量则将maxProcessor设为负数)
  3. 获取到Httpprocessor实例后调用其assign方法处理请求,注意这里不会阻塞(稍后会解释)
哪里不一样了呢?主要有两个地方:
  • 这里不是为每一个请求创建一个Httpprocessor实例,而是使用了pool
  • 这里Httpprocessor处理请求的assign方法不会阻塞
这两个地方就是关键!

首先第一个因为采用了pool,所以减少了创建Httpprocessor的数量,也就减少了创建大量对象的开销。

第二个也是最关键的地方就是assign方法:
首先我们在这里先强调一下,每一个Httpprocessor都是一个独立的线程,因为该类实现了runnable接口;其次,在defaultConnector初始化HttpProcessor pool的时候对每个Httpprocessor实例均调用了start方法,而start方法则调用了threadStart方法创建了该对象的守护进程,并且在run方法中的while循环中调用了await等待请求,而在await方法中却阻塞了!

代码如下:
    /**
     * Await a newly assigned Socket from our Connector, or <code>null</code>
     * if we are supposed to shut down.
     */
    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);

    }



为什么会阻塞呢?因为通过await方法中也有个while循环,通过available进入循环内部wait了,那available是什么意思呢?它为什么要等待呢?ok,因为available变量标示了当前有没有请求进来,如果没有请求进来当然要等待了。就这样,Httpprocessor线程进入了傻傻的等待中。。。它在等谁呢?或者说谁能把它唤醒呢?
这个时候关键的assign方法来了(该方法是Httpprocessor的),在defaultConnector中接受到请求后,获取Httpprocessor,调用了assign方法,并且还传入了socket对象,
该方法二话不说上来先判断available变量,如果有可用的请求,则立刻调用await等待(因为这说明该线程上的上一个请求还没处理完),而一旦没有了可用的请求,则立刻将传入的socket赋该实例变量socket,改变available的值为true,表示有请求进来啦,并且唤醒在该对象上等待的线程,那他唤醒了谁呢?当然就是await方法中阻塞等待的那位了,于是乎await方法中继续往下走,获取socket,改变available的值,表示请求已经被我拿到了,没请求啦,然后返回socket给run方法,run方法接着往下调用process方法处理请求。(这里还要注意一点,在await方法中最后调用了notifyAll,来唤醒在该对象上等待的其他线程,是谁呢?这里可以猜到就是assign方法中阻塞的线程)

assign方法如下:
    /**
     * Process an incoming TCP/IP connection on the specified socket.  Any
     * exception that occurs during processing must be logged and swallowed.
     * <b>NOTE</b>:  This method is called from our Connector's thread.  We
     * must assign it to our own thread so that multiple simultaneous
     * requests can be handled.
     *
     * @param socket TCP socket to process
     */
    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");

    }




到这里就很清楚了,为什么第四章的connector会更高效,主要是因为HttpProcessor是一个单独的线程了,它的处理逻辑assign不会阻塞defaultConnector线程,而defaultConnector在调用assign后会立即await等待下一个请求。总体来讲就是一句话,await()方法等待socket,assign()方法拿到socket赋值给成员变量socket,通知在httpprocessor对象上等待的线程处理socket。

再加上对defaultConnector简单的ab测试结果,同样的测试环境,同样的并发:

ok,以上就是整个defaultConnector的大概流程了,这里学习了两点,一个是pool的运用,另外一个就是线程。而且二者结合起来更强大,貌似还有个新名词,这里有另外一个总结,我感觉挺好的:
链接:
http://blog.csdn.net/cutesource/article/details/5081916

PS:
Coyote的介绍可以参考以下文章:
1. http://blog.csdn.net/wangchengsi/article/details/2973012
2. http://blog.csdn.net/zgmzyr/article/details/9853015

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值