以上是默认连接器类UML图。
Connector和Container是一对一的关系,从箭头的方向可以看出Connector是知道Container的,但反过来就不成立了。别外需要注意的是和上一章不同,HttpConnector和HttpProcessor是一对多的关系。
HttpConnector类
它实现了三个接口:
- org.apache.catalina.Connector,适配Catalina
- java.lang.Runnable,在一个单独的线程运行自己的实例
- org.apache.catalina.Lifecycle,每一个Catalina组件都要实现这个接口来维护自己的生命周期。实现了Lifecycle接口,你需要在创建HttpConnector接口后,需要调用initialize和start方法,在这个组件的生命周期里这两个方法只可以被调用一次。
接下来分析HttpConnector如何创建服务器套接字、如何维护一个HttpProcessor对象池、如何响应HTTP请求。
1,创建服务器套接字
initialize方法调用open方法返回一个java.net.ServerSocket实例并赋给serverSocket变量。open方法是通过一个服务器套接字的工厂类中获取的java.net.ServerSocket实例。
/**
* Return the server socket factory used by this Container.
*/
public ServerSocketFactory getFactory() {
if (this.factory == null) {
synchronized (this) {
this.factory = new DefaultServerSocketFactory();
}
}
return (this.factory);
}
2,维护HttpProcessor实例
在前面的章节中,每个HttpConnector只有一个HttpProcessor实例,所以它只能一次处理一个Http请求。在默认连接器中HttpConnector拥有一个HttpProcessor的实例池,每个HttpProcessor都运行在一个单独的线程上,因此,HttpConnector就可以同时响应多个HTTP请求。
// Create the specified minimum number of processors
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
这是HttpConnector类start方法创建初始数量HttpProcessor时的代码,newProcessor方法创建一个HttpProcessor的实例,recycle方法将创建出来的实例保存在一个Stack类型中,用Stack数据结构保存当前HttpConnector中所有HttpProcessor实例。
3,响应HTTP请求
在前面章节中,HttpConnector在一个while循环内等待HTTP请求直到它被关闭。
HttpProcessor processor = createProcessor();
以上代码获取一个HttpProcessor实例,createProcessor方法负责从对象池中返回一个HttpProcessor实例,如果对象池为空且当前对象数量没有超过最大值,则新建一个返回,并将其添加的对象池中。如果发生异常则返回空。
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
} catch (IOException e) {
;
}
continue;
}
如果返回的HttpProcessor为空,则关闭这个socket不进行响应。
如果不为空,则调用HttpProcessor的assign方法将这个socket分配给HttpProcessor进行处理。
processor.assign(socket);
现在,HttpProcessor负责读取这个socket的输入流并解析这个HTTP请求。在这里非常重要的一点是assign方法必须马上返回,不能等到HttpProcessor解析完成后再返回,这样HttpConnector才可以接着处理下一个请求。但是由于HttpProcessor实例是在自己单独的线程进行解析工作,所以这也并不难实现,接下来会在HttpProcessor中仔细讲解。
HttpProcessor类
在本章节中,重点主要是关注如何使用HttpProcessor类中的assign方法异步化,使用HttpConnector类可以同时响应多个HTTP请求。
在默认连接器中HttpProcessor实现了java.io.Runnable接口,HttpProcessor类的每一个实例都运行在各自的线程上,这里我们把它标为processor thread。对于每一个由HttpConnector创建的HttpProcessor实例的start方法都会被调用开启一个processor thread。
/**
* 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
process(socket);
// Finish up this request
request.recycle();
response.recycle();
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
在while循环中run方法的执行顺序是:获取一个Socket,处理这个Socket,调用HttpConnector的recycle方法回收HttpProcessor实例到对象池中。
void recycle(HttpProcessor processor) {
processors.push(processor);
}
从run方法中可以看出,在while循环中porcessor thread停在了await方法上。await方法阻塞了processor thread进程直到它从HttpConnector中获取到一个Socket,也就是说直到HttpConnector调用HttpProcessor实例的assign方法时。然而,await方法和assign方法并没有运行在同一个线程上,assign方法是由HttpConnector的run方法调用的。我们将HttpConnector类run方法运行的程序称为connector thread。那么如何让assign方法告诉await方法它已经被调用了呢?——通过使用一个boolean变量available以及java.lang.Object类中的wait和notifyAll方法。
wait方法可以阻塞当前线程直到另一个线程在这个对象上调用notify或是notifyAll方法
下面的HttpProcessor的assign方法和await方法:
assign:
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");
}
await方法:
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);
}
起初,processor thread启动时,available变量初始化值是false,HttpProcessor的run方法执行await方法,变量available为false时进入循环执行java.lang.Object的wait方法,阻塞了当前进程。每一个processor thread刚启动时都会阻塞到这个地方。它会一直阻塞,直到另一个线程在这个对象上执行了notify或是notifyAll方法。也就是说,processort thread调用wait方法后被阻塞,直到connector thread在HttpProcessor实例上调用notifyAll方法。
在assign方法中,当一个新Socket被分配时,connector thread调用了HttpProcessor的assign方法。此时available值为false,所以Socket会直接赋值给HttpProcessor实例的socket变量。
// Store the newly available Socket and notify our thread
this.socket = socket;
然后connector thread将变量available设置为true并且调用notifyAll方法,notifyAll方法会唤醒processor thread,这时available的值为true,所以在await方法会直接跳过while循环不执行wait方法。将HttpProcessor实例的socket赋给一个新建的Socket变量,设置available为false,调用notifyAll方法并返回Socket,这样我们就可以处理Socket了。
为什么await方法用了一个本地变量(socket)而不是直接返回实例的Socket呢?
——这是因为实例的socket变量可能在当前变量并没处理完成时被分配给下一个新来的Http请求的Socket。
为什么await方法需要调用notifyAll方法?
——只是用来预防当新请求的Socket到来时available变量为true。在这种情况下,connector thread会阻塞在assign方法中的while循环里,直到由processor thread调用了notifyAll方法。