这是阅读《how tomcat works》第四章后的总结。
本章主要讲的是tomcat4的defaultConnector(即HttpConnector,但是为了区别于第三章中的HttpConnector类,本文中称之为defaultConnector,也是因为他是tomcat4的默认connector。该类已经被遗弃了,由Coyote替代),文中提到connector是一个独立的模块,可以被其他实现所替换,已知的可用来替换的有:Coyote,mod_jk, mod_jk2,和 mod_webapp。其实想自定义一个connector类来替换defaultConnector很简单,该类只要满足一下三个提交即可:
- 实现org.apache.catalina.Connector接口
- 创建实现了org.apache.catalina.Reques接口的request对象
- 创建实现了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的时候采用了线程池。具体过程如下:
- 首先defaultConnector在初始化后会立即创建一个HttpProcessor pool
- 然后接收到一个请求后会从pool中获取一个HttpProcessor实例,如果没有则创建一个新的Httpprocessor实例,超过maxProcessors规定的数量则直接忽略该请求(如果不想限制processor数量则将maxProcessor设为负数)
- 获取到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