首先推荐一本好书,Budi Kurniawan以及Paul Deck所著的《How Tomcat Works》,这本书在豆瓣上的评分达到了史无前例的9.8分,而同为经典的《JAVA编程思想》以及GOF《设计模式》则为9.2分。序言是则精确定位了该书的读者群体,如下:
引用
How Tomcat Works is the only book that explains the internal workings of Tomcat, the open source project used by millions of Java developers. Unlike other Tomcat titles, it is unique because it does not simply covers the configuration or servlet development with Tomcat. Rather, this book is meant for advanced readers interested in writing their own Tomcat modules or in understanding more beyond servlet/JSP programming.
本书是当今仅有的解释Tomcat——这一被数以百万计JAVA开发人员所使用的服务器其内部工作机理的参考书。不想其它相关Tomcat书籍仅仅是叫你如何配置或者进行servlet编程,它的主要读者群体应该是那些感兴趣自己编写Tomcat模块、或是不满足于仅仅写servlet/JSP的开发人员。
Tomcat其核心是一个称为Catalina的模块,其又由两部分组成:Connector以及Container,前者主要负责连接的管理,后者主要负责servlet的解析,这是对Tomcat的最抽象概括。本文主要介绍Connector,Container会在后面的文章中介绍。
每当我们启动Tomcat的时候,其内部做了一下工作:
- 创建Connector
- 创建Container,并将其赋值给Connector
- 初始化Connector
- 启动Connector所在工作线程
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// make the application wait until we press any key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Tomcat的Connector启动会涉及到LifeCycle的管理,为简单起见,这里我们不必管这些,只需简单的认为是启动了Connector本身的后台线程(事实上它也是一个守护线程)。Connector的代码如下:
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
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);
} catch (AccessControlException ace) {
log("socket accept security exception: " + ace.getMessage());
continue;
} catch (IOException e) {
if (started && !stopped)
log("accept: ", e);
break;
}
// 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);
// The processor will recycle itself when it finishes
}
// Notify the threadStop() method that we have shut ourselves down
synchronized (threadSync) {
threadSync.notifyAll();
}
}
前面的逻辑,有过socket编程基础的朋友都不难理解,仅仅是创建一个socket,并调用serverSocket的accept方法,该方法会阻塞直到请求来到。之后创建了一个HttpProcesser实例,代码如下:
HttpProcessor processor = createProcessor();
该方法的内部实现如下:
/**
* Create (or allocate) and return an available processor for use in
* processing a specific HTTP request, if possible. If the maximum
* allowed processors have already been created and are in use, return
* <code>null</code> instead.
*/
private HttpProcessor createProcessor() {
synchronized (processors) {
if (processors.size() > 0)
return ((HttpProcessor) processors.pop());
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
return (newProcessor());
} else {
if (maxProcessors < 0) {
return (newProcessor());
} else {
return (null);
}
}
}
}
在Tomcat的Connector实现中,通过一个池(pool)来管理HttpProcessor对象,每一个HttpProcessor都是一个单独的线程,因此HttpConnector可以同时服务多个Http请求。Tomcat通过一个java.io.Stack来保存多个HttpProcessor对象,如下:
/**
* The set of processors that have been created but are not currently
* being used to process a request.
*/
private Stack processors = new Stack();
在HttpConnector中,HttpProcessor的实例数量由两个变量来决定:minProcessors以及maxProcessors。默认情况下,分别设置为5和20,可以通过相应的setMinProcessors以及setMaxProcessors方法来修改该值。Tomcat启动时,HttpConnector会创建数量为minProcessors的HttpProcessor实例并放入池中;如果在某个时刻,请求的数量会多于当前池中HttpProcessor的数量,Tomcat会不断地创建HttpProcessor实例并放入池中,直至池中的数量达到maxProcessors。此时如果再有请求到来,Tomcat会选择忽略这些Http请求;当然,你可以讲maxProcessors设置为负数以取消这个限制。另外,代码中的curProcessors保存了当前池中HttpProcessor实例的数量。
再看前面createProcessor的代码,每次在池中创建新的Processor的时候,都会调用newProcessor方法,其实现如下:
/**
* Create and return a new processor suitable for processing HTTP
* requests and returning the corresponding responses.
*/
private HttpProcessor newProcessor() {
// if (debug >= 2)
// log("newProcessor: Creating new processor");
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
if (processor instanceof Lifecycle) {
try {
((Lifecycle) processor).start();
} catch (LifecycleException e) {
log("newProcessor", e);
return (null);
}
}
created.addElement(processor);
return (processor);
}
如前所述,我们照常忽略代码中的LifeCycle,这将会在以后的文章中解释;从上面可以看出,每次创建Processor,都会通过调用start方法,直接启动后台线程,事实上,Tomcat一旦启动Processor线程,就不会关闭它,即使处理完毕被放回到池中;除非是整个Tomcat被关闭。Processor后台线程的工作很简单,它会一直等待,一旦有socket返回(该Processor被分配到处理request),就立即处理,其代码如下:
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
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();
}
}
除非stopped置位,否则HttpProcessor会一直遵循“等待——处理——回收”这一流程周而复始。问题是,等待谁?看下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);
}
available的意思,应该是指目前连接的状态,它被声明为全局变量。整个await方法与下面的assign方法构成了一个同步队列,或者称为“单商品生产者消费者队列”。翻看前面粘贴Connector中的代码,可知其是在每次处理连接的时候调用的。
/**
* 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");
}
这里我们模拟两个线程(调用assign方法的Connector Thread以及调用await的Processor Thread)的通知方式。一开始由于没有请求的连接,因此available设置为false,因此线程一直在while循环中(确切地说是在wait方法中),直到其它线程调用notify或者notifyAll将其唤醒。
当连接请求到达时,一个socket被assign,此时由于前面available中已经被设置为false,因此跳过整个while-wait循环而直接进入到下面语句:
this.socket = socket;
available = true;
notifyAll();
Connector Thread继续将available设置为true,并直接返回;此时它还不能接受另外的连接请求,因为available为true会导致它一直处在while-wait循环中(然而这只是表面原因,本质是由于该连接还没有被处理导致socket变量没能释放);接着notifyAll将会唤醒前面的Processor Thread。此时由于available被设为true,await方法会跳出while-wait循环而直接进入到下面语句:
// 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方法创造了一个Socket的局部变量返回,同时释放了this.socket这一个实例变量,将available设置为false并调用notifyAll唤醒Connector Thread。现在,它又可以接收请求连接了……
整个过程中,Connect所需的时间要远小于Process,因此Tomcat将Connector与Processor分开来,并通过await与assign两个方法进行同步,以达到最优的效率。