浅析tomcat原理
上上个星期,看了一下how tomcat works这本书,今天捡起来看一会,发现忘得有点快,特地写点东西,加深一下记忆。因为书讲的是tomcat4,5的内容,比较旧了,所以和最新的tomcat的差距还是有点大的。而且还没看完,以后再补充吧。
而java实现web最简单的方式就是对socket和serversocket的封装。也是早期tomcat的实现方式。
计算机网络之间的通信是基于端口之间通信,对于服务器,从端口读取数据,也就是inputStream, 往端口写数据,也就是outputStream。
serverSocket = new ServerSocket(8080, 1, InetAddress
.getByName("127.0.0.1"));
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
HttpServletRequest是请求,也就是客户端往服务器端口发送消息,服务器从端口读取数据,所以HttpServletRequest封装了上面的inputStream。 同理HttpServletResponse封装了上面的outputStream。
ServerSocket 监听本机端口, 我们可以去浏览器输入: http://127.0.0.1:8080 然后后台读取程序中读取一个叫做index.html的文件(FileInputStream->bytes),output.write(bytes, 0, lenth). 将文件内容输出到端口,网页中就会显示内容了。是不是有点熟悉。当然tomcat肯定不是这么简单的实现,但是最基本的原理就是如此。
当然这个是静态资源,如果是动态servlet的处理,大致是通过类加载器,加载进内存,反射得到对象。然后根据request请求信息,判断是请求静态文件还是动态servlet,然后执行servlet的service(request, response)方法;
Connector 连接器
直接跳到连接器,有点太快了,书中还有很多小细节,门面设计模式, response中getWriter方法就是封装了outputStream… 推荐去看一下。
连接器,顾名思义,就是用来处理连接的。
private int port = 8080;
private ServerSocket serverSocket = null;
这是我直接在tomcat4源码中HttpConnector拷贝过来的两个参数,然后再来看看这个类的一个重要方法。
public void run() {
// Loop until we receive a shutdown command
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
} catch () {
...
}
HttpProcessor processor = createProcessor();
...
processor.assign(socket);
...
}
...
}
一个循环,等待连接的到来,得到一个socket对象, 然后创建一个处理器,去处理这个socket的请求。然后继续等待下一个连接accept()。
当然如果tomcat这样顺序进行,那肯定是不行的,所以一个连接器有一个processor(处理器)的对象池。而每个处理器实现了runnable接口,在创建的时候就启动了线程,然后阻塞自己,直到自己调用了assign() 方法,就是上面的那个方法,然后执行处理request的方法,处理servlet还是静态资源。完成后继续阻塞自己。
所以tomcat的connector有多个processor,请求来了调用一个processor去执行,而本身不需要等待这个processor完成,继续接收下一个请求。是一种异步实现的感觉。这样可以处理多个请求,而无需阻塞。这也是早期bio的解决方案。现在的解决方案应该是nio了。
final class HttpProcessor implements Lifecycle, Runnable {
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();
}
}
}
就是在await()方法阻塞,然后释放,执行process完成,循环继续阻塞。
processor 处理器
首先看看这个processor做了什么吧。
- 创建HttpServletRequest, HttpServletResponse对象。
- 解析连接
- 解析请求
- 解析头部, 给request.setHeader。
- 隐式调用了servlet的service方法。 虽然从这里开始,但并不是直接调用。
具体是怎么解析的请求,我没有深入去了解,大概就是拆分字符串,截取之类的吧,还是看看这个类最重要的方法。process() 方法。
private void process(Socket socket) {
...
try {
input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
} catch (Exception e) {
...
}
keepAlive = true;
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
} catch (Exception e) {
...
}
...//这里解析请求,给丰富request和response。
try {
...
if (ok) {
// 重点。 画上!!!
connector.getContainer().invoke(request, response);
}
} catch (ServletException e) {
...
}
}
大致就是上面,可以看出,request和response封装了输入输出流,然后解析请求, 调用了container的invoke(request, response)方法。所以我们希望见到的service方法就藏在这里面了。
Container 容器
这个可以说是tomat中最被人熟知的东西之一。tomcat4中有四大容器,以我的理解简单介绍一下, 可能有点不对。
- Engine 引擎, 启动一个tomcat服务,也就是启动一个引擎
- host 虚拟主机, 一个Engine启动,下面项目都会启动,localhost:8080/work1,work2
- context 上下文, 一个项目对应一个上下文,通过map映射到不同的servlet。
- wrapper 包装器, 一个wrapper对应一个servlet。
上面的四个都实现了Container接口,就先来谈谈wrapper吧。connector中有个方法,setContainer(Container container); 如果一个wrapper被一个connector绑定,那么回到上面画重点的方法。
connector.getContainer().invoke(request, response);
wrapper 包装器
所以点进wrapper类中找寻这个方法,发现没有,这个方法的实现在他的父类ContainerBase中有实现。
public void invoke(Request request, Response response) throws IOException, ServletException {
pipeline.invoke(request, response);
}
出现了一个新的东西,叫做管道(pipeline)。点下去,会发现很麻烦,先来捋一捋Container中这些的关系。
container 中包含了一个pipeline, 而pipeline执行invoke方法是创建了一个pipelineContext对象,并执行invokeNext方法。 然后pipelineContext对象就会去执行Valve的invoke方法。当然,执行方式有点像递归,一直往下,直到执行basicvalve的invoke方法,这个方法中会隐式执行service方法,然后再回去执行上一个valve。看看basicvalve的invoke方法。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
...
StandardWrapper wrapper = (StandardWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse)
hres = (HttpServletResponse) sres;
...
try {
if (!unavailable) {
// 划重点。。。
servlet = wrapper.allocate();
}
} catch (ServletException e) {
...
}
ApplicationFilterChain filterChain = createFilterChain(request, servlet);
try {
...
if ((servlet != null) && (filterChain != null)) {
// 划重点2。。。
filterChain.doFilter(sreq, sres);
}
...
}
}
通过getContainer得到wrapper, wrapper.allocate(). 这个方法中通过反射之类的得到了servlet对象,返回。然后创建一个过滤器链, 调用doFilter方法传递request和response。我们再看看doFilter方法。
if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) {
servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
} else {
servlet.service(request, response);
}
这个方法中,终于找到了调用service方法了。当然上面说的都是wrapper。一个wrapper只对应一个servlet,但是一个项目肯定有多个servlet,那么这就涉及到我们熟悉的Context(上下文)了。
context 上下文
我们再回到connector,connector.setContainer(context). 如果connector设置的容器是上下文。再来分析一下。想起我们之前的那个方法。
connector.getContainer().invoke(request, response);
context的invoke和wrapper一样,pipeline.invoke, 然后同样是pipelineContext.invokeNext。 那么不一样的地方在哪里呢? 那就是basicvalve, 我们称为contextValve。 我们看看contextValve的invoke方法。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
...
Context context = (Context) getContainer();
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI, (HttpServletResponse) response.getResponse());
return;
}
if (wrapper == null) {
notFound((HttpServletResponse) response.getResponse());
return;
}
response.setContext(context);
wrapper.invoke(request, response);
}
相当简洁, 就是context从request中读取到类似 /IndexServlet, 然后就会去map中搜索,找到处理这个IndexServlet的servlet。也就是wrapper,执行wrapper的invoke,又回到了上面wrapper的方法,记得上面的说的吗,一个wrapper只处理一个servlet。而一个上下文对应多个servlet。 而这里的map就是我们经常写的xml映射关系。
<servlet-mapping>
<servlet-name>IndexServlet</servlet-name>
<url-pattern>/IndexServlet</url-pattern>
</servlet-mapping>
当然这个map有一个专门的类ContextMapper, 这个就不深究如何映射的。还有上面类加载,反射servlet的方法,其实也有一个专门的接口叫Loader。我也没看太懂。
然后至于host和engine的实现,没看完,看看以后看完了有时间再来补上。原理应该差不多。