Tomcat剖析(四):Tomcat默认连接器(1)
目录:
这一节比较好玩,好玩在于如果大家如果看得懂的话,那么这一节可以学到很多东西,反之可能会很困惑。
本节对应《深入剖析Tomcat》第四章。
大家一定要先去下载Tomcat4的源码,放入eclipse中查看,否则肯定会不知所云。
代码可以在我的github上找到下载,拷贝到eclipse,然后打开对应包的代码即可。
https://github.com/zebinlin/Tomcat4-src
如发现编译错误,可能是由于jdk不同版本对编译的要求不同导致的,可以不管,供学习研究使用。
在“Tomcat剖析(三):连接器”中的连接器已经能较好的运行,既使用了线程启动,也完成了对大部分请求解析,但是仍然后很多不足。
一个 Tomcat 连接器必须符合以下条件:
1. 必须实现接口 org.apache.catalina.Connector。
2. 必须创建请求对象,该请求对象的类必须实现接口 org.apache.catalina.Request。
3. 必须创建响应对象,该响应对象的类必须实现接口 org.apache.catalina.Response。
可以看到,上节的简单连接器基本的条件都没有实现。
Tomcat4 的默认连接器类似于上节的简单连接器。它等待前来的 HTTP 请求,创建 request和 response 对象,然后把 request 和 response 对象传递给容器(上节只是交给响应的处理器Processor处理)。连接器是通过调用接口org.apache.catalina.Container 的 invoke 方法来传递 request 和 response 对象的。这样说可能不清楚。下面慢慢讲。
这一节的核心就是对上一节基础上的改进:HttpConnector 如何创建一个服务器套接字,它如何维护一个 HttpProcessor 对象池,还有它如何处理 HTTP 请求。
写了一半才发现要写完全部知识点需要用灰常灰常大的篇幅,所以有些内容留到下一节再讲,这一节讲解Tomcat是如何处理多个请求的。
总体来说,处理用户请求有以下几个步骤:
1.先创建serverSocket实例
2.建立HttpProcessor处理器池,等待请求
3.请求到来时从处理器池中获取HttpProcessor实例,HttpProcessor实例负责解析请求
4.完成请求后将实例返回到池中。实现对多请求的处理。
首先是Bootstrap.java。
为什么是这个启动类呢?因为可以通过启动过程比较完整的分析代码。
对于什么是SimpleContainer,为什么一个连接器要setContainer(container)将指定容器设置到连接器中,大家可以先不管,在下一节对本节的补充中大家就知道为什么了,最后还会再回头看这个类。
还有需要先提前注意一点,这里的HttpConnector已经是Tomcat4中核心包中的代码,不再是属于ex**中的类,有点高级,哈哈。
1 package ex04.pyrmont.startup; 2 3 import ex04.pyrmont.core.SimpleContainer; 4 import org.apache.catalina.connector.http.HttpConnector; 5 6 public final class Bootstrap { 7 8 public static void main(String[] args) { 9 10 HttpConnector connector = new HttpConnector(); 11 SimpleContainer container = new SimpleContainer(); 12 connector.setContainer(container); 13 try { 14 connector.initialize(); 15 connector.start(); 16 17 System.in.read(); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 }
接下来大家看看HttpConnector.java,可以发现实现除了Connector接口外,还实现了Runnable接口和Lifecycle接口。
为什么要实现Runnable接口,因为它的实例可以运行在自己的线程上,也就是需要启动线程。
Lifecycle 将以后解释,现在你不需要担心它,只要明白通过实现 Lifecycle,在你创建 HttpConnector 实例之后,你应该调用它的 initialize 和 start 方法。这两个方法在组件的
生命周期里必须只调用一次。
一、连接器初始化:完成服务器端ServerSocket的创建
对应connector.initialize()
可以看到HttpConnector.java的initialize方法的作用是完成服务器端的ServerSocket的创建,并赋值给HttpConnector的实例变量serversocket中,里面调用了这个类的私有方法open();
serverSocket = open();
看看open(),open()方法负责创建SeverSocket这个方法有下面这些关键语句
private ServerSocket open(){ ServerSocketFactory factory = getFactory(); try { return (factory.createSocket(port, acceptCount, is)); } catch (BindException be) { throw new BindException(be.getMessage() + ":" + address + ":" + port); } }
getFactory()是用单例模式返回具体工厂,即代码中的DefaultServerSocketFactory实例。DefaultServerSocketFactory对象负责创建ServerSocket对象,也就factory.createSocket(...)。
public ServerSocketFactory getFactory() { if (this.factory == null) { synchronized (this) { this.factory = new DefaultServerSocketFactory(); } } return (this.factory); }
通过对比前一节或者ex03包下的HttpConnector类,可以发现一个简单的创建ServerSocket对象变得复杂许多,唯一不变的就是创建的地点都是在HttpConnector.java中,只是不再是在启动HttpConnector线程时创建,而是在之前创建,不依赖于HttpConnector线程的启动。
下面是上一节创建 ServerSocket对象的方式。
ServerSocket serverSocket = null; int port = 8080; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); }
二、启动连接器
对应connector.start();
下面是这个方法关键代码。
public void start(){ threadStart();//启动HttpConnector线程 while (curProcessors < minProcessors) { if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) break; HttpProcessor processor = newProcessor();//创建HttpProcessor池 recycle(processor); } }
启动HttpConnector线程threadStart()放在本小节后半部分说,先介绍处理器池。
HttpConnector 维护一个 HttpProcessor 的实例池,从而避免每次创建 HttpProcessor 实例。
这些 HttpProcessor 对象是存放在HttpProcessor.java中一个叫 processors 实例的 java.io.Stack 中:
private Stack processors = new Stack();
启动连接器概括1:启动HttpConnector线程,每一趟while循环new出HttpProcessor对象,并调用recyle方法将对象放入代表池的processors栈中。
void recycle(HttpProcessor processor) { processors.push(processor);//将创建的实例压入栈中 }
如何创建HttpProcessor实例的:newProcessor()方法
这个方法获取到的信息是:创建出一个HttpProcessor对象后就立即启动这个线程
启动连接器概括2:启动HttpConnector线程,一个while循环过后,创建了一些HttpProcessor实例,然后启动它们的线程,最后通过recyle()方法放入processors实例中
启动Processor线程后发生了什么,稍后再说。
private HttpProcessor newProcessor() { //创建实例 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); }
看看HttpProcesor.java的构造方法
构造方法中,从connector中获取端口,包括代理端口
同时创建HttpRequestImpl和HttpResponseImpl实例,以便于请求到来时不用再创建这些对象,只需要再放入相应的请求信息就行。
同时HttpConnector.java通过创建处理器实例,在这个过程绑定了请求和connector之间的关系,即 request.setConnector(this);
好了,现在对启动连接器过程发生了什么可以概括为:
启动连接器概括3:启动HttpConnector线程,一个while循环过后,创建了一些HttpProcessor实例;创建每一个HttpProcessor实例过程中,都会创建请求和响应对象,避免请求到来时再创建,并绑定连接器与请求之间的关系;然后启动处理器线程,最后通过recyle()方法放入processors实例中。
public HttpProcessor(HttpConnector connector, int id) { super(); this.connector = connector; this.debug = connector.getDebug(); this.id = id; this.proxyName = connector.getProxyName(); this.proxyPort = connector.getProxyPort(); this.request = (HttpRequestImpl) connector.createRequest(); this.response = (HttpResponseImpl) connector.createResponse(); this.serverPort = connector.getPort(); this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]"; }
public Request createRequest() { HttpRequestImpl request = new HttpRequestImpl(); request.setConnector(this); return (request); }
接下来看看HttpProcessor线程启动后发生了什么事,当然就是它的run方法。
好了,现在比如有10个处理器线程,那么从注释中可以看到,启动一个处理器线程后,这个线程通过await就一直处于等待请求状态了,请求到来后就可以调用process(socket)处理
启动连接器概括4:启动HttpConnector线程,一个while循环过后,创建了一些HttpProcessor实例,这些实例中没有用户的socket信息;创建每一个HttpProcessor实例过程中,都会创建请求和响应对象,避免请求到来时再创建,并绑定连接器与请求之间的关系;然后启动处理器线程,这个线程启动后await()方法进入阻塞状态等待请求;通过recyle()方法放入processors实例中;如果请求到来,获取一个处理器,处理这个请求。
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); //连接器回收处理器 } }
听起来可能有些迷糊,因为还没将连接器线程的启动。
我们知道HttpConnector实现了Runnable接口,Bootstrap.java中调用connector.start(),最后通过threadStart()启动HttpConnector线程
threadStart()方法就是创建创建了HttpConnector线程,所以会调用HttpConnector类的run方法。
private void threadStart() { log(sm.getString("httpConnector.starting")); thread = new Thread(this, threadName); thread.setDaemon(true); thread.start(); }
HttpConnector的run方法究竟做了什么呢?
还记得上一节中是如何等待客户端请求的吗?当然就是调用serverSocket的accept方法
经过默认连接器改进了,但是在启动HttpConnector线程后,run方法依然是使用同样的方式等待用户请求。只是更加具体。
下面贴上theadStart()方法中的关键代码。
public void run() { while (!stopped) { Socket socket = null; try { socket = serverSocket.accept();//等待用户请求,阻塞 } catch (AccessControlException ace) { continue; } catch (IOException e) { //省略 continue; } HttpProcessor processor = createProcessor(); //从池中获取处理器实例 if (processor == null) { try { socket.close(); } catch (IOException e) { ; } continue; } processor.assign(socket); //将请求的socket交给得到的处理器实例中 } }
简单说说createProcessor()方法:
在 HttpConnector 中,创建的 HttpProcessor 实例数量是有两个变量决定的: minProcessors和 maxProcessors。默认情况下, minProcessors 为 5 而 maxProcessors 为 20,但是你可以通过setMinProcessors 和 setMaxProcessors 方法来改变他们的值。
protected int minProcessors = 5;
private int maxProcessors = 20;
开始的时候, HttpConnector 对象创建 minProcessors 个 HttpProcessor 实例。如果一次有比 HtppProcessor 实例更多的请求需要处理时, HttpConnector 创建更多的 HttpProcessor 实例,直到实例数量达到 maxProcessors 个。在到 达这点之后,仍不够 HttpProcessor 实例的话,请来的请求将会给忽略掉。如果你想让 HttpConnector 继续创建 HttpProcessor 实例的话,把maxProcessors 设置为一个负数。还有就是变量 curProcessors 保存了 HttpProcessor 实例的当前数量。
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); } } } }
从上面的递进说明中。可以知道整个流程是这样子的:
首先connector.initialize方法创建ServerSocket实例。connector.start()方法中启动HttpConnector线程,这个线程通过serverSocket.accept()进入等待用户请求状态。
随后connector.start()方法创建了若干个HttpProcessor处理器实例,同时启动了处理器线程,由于这些处理器实例中没有用户的socket信息,无法处理请求,所以全部进入阻塞状态,即await方法。当用户请求到来时,通过assign方法将socket放入处理器实例中,以便让处理器处理用户请求。
那Tomcat4是如何实现同时处理多个请求的呢?
就要看assgin和await方法了。
用户请求到来,得到了用户的socket,调用assign方法,因为avaiable默认是false,所以跳过while需要,将socket放入获取到的处理器实例中,同时将avaiable设为true,唤醒线程,此时await方法中的wait方法被唤醒了,同时因为avaliable为true,跳出循环,将avaiable设为false重新进入阻塞,得到用户的返回用户的socket,最后就能够通过process处理请求了。
synchronized void assign(Socket socket) { while (available) { //avaiable默认是false,,第一次执行时跳过while try { wait(); } catch (InterruptedException e) { } } this.socket = socket; //将从池中获取到的HttpProcessor实例中的socket变量赋值 available = true; notifyAll();//唤醒线程。 }
private synchronized Socket await() { while (!available) {//默认是false,所以进入循环阻塞,因为处理器实例没有socket信息, try { wait(); } catch (InterruptedException e) { } } Socket socket = this.socket; //得到了socket available = false; //重新进入阻塞 notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); }
疑问:
为什么 await 需要使用一个本地变量(socket)而不是返回实例的 socket 变量呢?
因为这样一来,在当前 socket 被完全处理之前,实例的 socket 变量可以赋给下一个前来的 socket。
为什么 await 方法需要调用 notifyAll 呢?
这是为了防止在 available 为 true 的时候另一个 socket 到来。在这种情况下,连接器线程将会在 assign 方法的 while 循环中停止,直到接收到处理器线程的 notifyAll 调用。
process方法处理请求后,最后通过connecotr.recyle()方法回收处理器,即将处理器压回处理器池中,
void recycle(HttpProcessor processor) { processors.push(processor);//将创建的实例压入栈中 }
process方法就是处理HTTP请求的方法了,本节太长了,所以下一节再续上。
这篇博客写了我4个小时,希望大家看过之后有所帮助。
如果觉得还不错,可以推荐一下或加关注哟,觉得写得不好的也体谅一下。
附:
代码可以在我的github上找到下载,拷贝到eclipse,然后打开对应包的代码即可。
https://github.com/zebinlin/Tomcat4-src
如发现编译错误,可能是由于jdk不同版本对编译的要求不同导致的,可以不管,供学习研究使用。
原文:http://www.cnblogs.com/lzb1096101803/p/4841140.html