本文提及的tomcat默认连接器是指tomcat4的默认连接器,即使默认连接器已经被弃用,被性能油价优秀的Coyote连接器所取代,但是他仍然是一个不错的学习工具
tomcat连接器是一个可以插入servlet容器的独立模块。已经存在相当多的连接器了,包括Coyote, mod_jk,mod_jk2 和 mod_webapp。一个tomcat连接器需要符合下列条件:
- 必须实现接口 org.apache.catalina.Connector
- 必须创建请求对象,该请求对象的类必须实现接口 org.apache.catalina.Request
- 必须创建响应对象,该响应对象的类必须实现接口 org.apache.catalina.Response
tomcat的默认连接器类似于第三章的简单连接器。他等待前来的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 方法,管理会话,记录出错日志等等。
Connector接口
tomcat连接器必须实现 org.apache.catalina.Connector 接口,在这个接口中,最重要的方法莫过于getContainer,setContainer, createRequest 和 createResponse。
setContainer是用来关联连接器和容器用的。getContainer返回关联的容器,createRequest 和createResponse分别创建HTTP链接对象和请求对象。
连接器的UML图:
可以看出,在此图中,连接器和容器是1对1 的关系,并且连接器包含有容器的实力。
HttpConnector类
第三章已经介绍了org.apache.catalina.connector.http.HttpConnector的简化版,本章主要看看与第三章的不同之处:HttpConnector 如何创建一个服务器套接字,它如何维护一个 HttpProcessor 对象池,还有它如何处理 HTTP 请求。
创建一个服务器套接字
HttpConnector 的 initialize 方 法 调 用 open 这 个 私 有 方 法 , 返 回 一 个java.net.ServerSocket 实 例 , 并 把 它 赋 予 serverSocket 。 然 而 , 不 是 调 用java.net.ServerSocket 的构造方法, open 方法是从一个服务端套接字工厂中获得一个ServerSocket 实例。
维护 HttpProcessor 实例
HttpConnector 实例一次仅仅拥有一个 HttpProcessor 实例,所以每次只能处理一个 HTTP 请求。在默认连接器 中, HttpConnector 拥有一个HttpProcessor 对象池,每个HttpProcessor 实例拥有一个独立线程。因 此, HttpConnector 可以同时处理多个 HTTP 请求。HttpConnector 维护一个 HttpProcessor 的实例池,从而避免每次创建 HttpProcessor 实例。这些 HttpProcessor 实例是存放在一个叫 processors 的 java.io.Stack 中:
private Stack processors = new Stack();
连接器创建的processor对象由两个变量决定:minProcessors和 maxProcessors。默认创建minProcessors个,如果HTTP请求超过minProcessors,则会创建更多的processor,直到maxProcessors个,超过这个数量的请求将会被忽略。如果你想让 HttpConnector 继续创建 HttpProcessor 实例的话,把maxProcessors 设置为一个负数。还有就是变量 curProcessors 保存了 HttpProcessor 实例的当前数量。
为 HTTP 请求服务
HttpConnector 类在它的 run 方法中有其主要的逻辑。 run 方法在一个服务端套接字等待 HTTP 请求的地方存在一个 while 循环,一直运行直至 HttpConnector 被关闭。
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
对每个前来的 HTTP 请求,会通过调用私有方法 createProcessor 获得一个 HttpProcessor实例。大部分时候createProcessor 并不是新建了一个对象,而是从栈中取出一个对象,如果栈是空的并且没有超过HttpProcessor 实例的最大数 量, createProcessor 将会创建一个。然而,如果已经达到最大数量的话, createProcessor 将会返回 null。出现这样的情况的 话,套接字将会简单关闭并且前来的 HTTP 请求不会被处理。
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 类是第 3 章中有着类似名字的类的全功能版本。你已经学习了它是如何工作的,在本章中,我们看的是HttpProcessor 类怎样让 assign 方法异步化,这样 HttpProcessor 实例就可以同时间为很多 HTTP 请求服务了。
在第三章中,HttpProcessor 类的 process 方法是同步的。因此,在接受另一个请求之前,它的 run 方法要等待 process 方法运行结束。
public void run() {
...
while (!stopped) {
Socket socket = null;
try {
socket = serversocket.accept();
}
catch (Exception e) {
continue;
}
// 同步
HttpProcessor processor = new Httpprocessor(this);
processor.process(socket);
}
}
在默认连接器中,HttpProcessor实现了Runnable接口并且每个HttpProcessor实例运行在自身线程上,这个线程被叫做处理器线程。
下面是HttpProcessor类的run方法:
public void run() {
while (!stopped) {
Socket socket = await();
if (socket == null)
continue;
try {
process(socket);
}
catch (Throwable t) {
log("process.invoke", t);
}
connector.recycle(this);
}
synchronized (threadSync) {
threadSync.notifyAll();
}
}
run 方法中的 while 循环按照这样的循序进行:获取一个套接字,处理它,调用连接器的recycle 方法把当前的 HttpProcessor 实例推回栈。这里是 HttpConenctor 类的 recycle 方法:
void recycle(HttpProcessor processor) {
processors.push(processor);
}
请求对象和响应对象
关于请求对象和响应对象,在第三章已经详细介绍,这里就不在赘述。
处理请求
请求的出来主要是交给了HttpProcessor类的process方法,他主要的任务是:
- 解析连接
- 解析请求
- 解析头部
具体的过程和第三章讲述的类似。