其实在第三章,就已经有了连接器的样子了,不过那只是一个学习工具,在这一章我们会开始分析tomcat4里面的默认连接器。
连接器
Tomcat连接器必须满足以下几个要求
1 实现org.apache.cataline.Connector接口
2 负责创建实现了org.apache.cataline.Request接口的request对象
3 负责创建实现了org.apache.cataline.Response接口的response对象
这里默认的连接器的原理很简单,就是等待http请求,创建request与response对象,然后剩下任务交给Container接口的invoke方法处理。(这里是命令模式,具体问题咱们后面谈)
public void invoke(org.apache.cataline.Request request,org.apache.cataline.Response response);
http1.1的新特性
1持久连接
2块编码
3状态码100
这三点在本章的连接器中也有体现,但本人觉得这部分不是这篇博客要说明的重点,因而不过多的作解释。
tomcat总的结构框图
其实这部分内容是在书的前言部分,本来在这一系列读书笔记一开始的时候,我就得给朋友们聊聊这部分,但当时确实没怎么看懂,就只能拖到现在了。
一个servlet容器(Container)可以对应若干个连接器(Connector)。连接器主要干的事就是接收http请求,产生request与response,然后将处理过程交给容器;
容器干的事就是调用相应的servlet对象(准确的说是调用servlet的service方法,就用传过来的request与response作参数)
Connector接口
类图如下
接口情况如下
根据上文,大家也应该能猜出来,我在图中标示的几个方法也就是接口里面最重要的。
HttpConnector类
它既实现了上面说的Connector接口,也实现了Lifecycle接口(这个接口,我们后面再细谈)与第三章不同,我们这里的HttpConnector类多了三个功能
1创建服务器套接字
在连接器初始化的时候,会调用HttpConnector类的open方法来填充成员变量serverSocket。
open内部是通过先产生一个DefaultServerSocketFactory,在从工厂里按照端口号及acceptCount(本connector能同时支持的http请求)来创建ServerSocket对象(其实这里我也不明白为什么不直接new出那个socket 只是为了使用者 生产者分离吗)
2维护httpprocess实例
我们刚才说了一个connector关联着多个httpprocessor对象。这些对象是以Stack的数据结构存储的(先进后出)
private Stack processors = new Stack();
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);
}
}
}
}
上面产生一个新processor的方法中有两个参数,maxProcessor是connector支持的最大prcessor量,另一个是目前的process量。上面的逻辑意义不解释。两个参数都是可以通过set方法设置的。
3提供http请求服务
与前面几章的类似,主要的工作都在run方法内部
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();
....
熟悉的accept方法
然后就是
HttpProcessor processor = createProcessor(); //分线程
if (processor == null) {
try {
socket.close();
} catch (IOException e) {
;
}
continue; //话说这个continue是干什么的?
}
processor.assign(socket); //主线程
需要注意的是,上面的代码我做了注释,有两个线程了,因此processor.assign只会在主线程中调用一下,不需要等待解析的结果,具体的解析在另一个分线程里了,这样做的结果就是可以同时处理多个http请求。
只有createProcessor为什么就产生了新的线程,因为
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();
HttpProcessor类
既然上面都说了creatprcessor方法会启动一个新的线程,而且也出现了异步的assign方法,那么咱们就看看HttpProcessor类的assign与run方法。
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); //将自己重新push进stack里
}
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");
}
在run里面又有一个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);
}
里面有个available,似乎是关键,再看看
/**
* Is there a new socket available?
*/
private boolean available = false;
我就说一句,最开始available是false,我们creatProcessor时,调用了run方法,又调用了await方法,因为available为false,while循环起作用了,调用object的wait()这个分线程就停止在这了。
processor.assign(socket); //主线程
看这个代码,再看assign(),available为false,跳过循环,将available改为true,(阻塞自己)然后通知所有进程。
run里的await方法因为available为true得以运行,接着就是 process(socket);
Request对象
默认连接器中的request对象是org.apache.cataline.Request接口的实例,uml类图如下
Response对象
uml类图如下
处理请求
这部分确实比较麻烦,可以理解为整体就是一个大循环,当httpprocessor实例终止,或者链接断开停止循环。下来就是对request对象与response对象的初始化工作
然后是parseConnection(),parseRequest(),parseHeaders()这三个方法分别解析连接,请求与请求头。
其中解析请求与第三章的内容差不多,对请求头的解析使用了字符数组,避免了代价高昂的字符串操作。
完成解析后,process方法会把request与response对象作为参数传递给servlet容器的invoke方法
connector.getContainer().invoke(request, response);
简单的Container应用程序
现在再说说Container,我们使用的实现类是SimpleContainer(已经移除了StaticResourceProcessor类,因此不能再访问静态资源)我们只看看他的invoke方法
public void invoke(Request request, Response response)
throws IOException, ServletException {
String servletName = ( (HttpServletRequest) request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
确实,如果你看了书的前三章,就会发现这里确实没有什么可说的,很简单。
Bootstrap
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(); //注意这里还只是单纯的函数调用,目前跟线程还没关系,还没run呢
// make the application wait until we press any key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
初始化部分包含了一下lifecircle的问题,不用太在意,以后我们会说。
下一节我们说说tomcat里面的命令模式
博客中图片均来自how tomcat works一书