一、请求处理流程图
上图中有两个重要的组件:Connector和Engine,接下来重点介绍下这两个组件。
二、Http服务器-Connector
1、由上图可知,Connector组件包含三部分:Http11NioProtocol、Mapper、CoyoteAdapter。Http11NioProtocol包含NioEndpoint和Http11ConnectionHandler,NioEndpoint是Http11NioProtocol中负责接收处理socket的主要模块;Http11ConnectionHandler是连接处理器。NioEndpoint主要是实现了socket请求监听线程Acceptor、socket NIO poller线程、以及请求处理线程池。
NioEndpoint的NIO处理宏观流程为:
由这个流程图可以看到,NioEndpoint的处理逻辑可以参考下Netty中的NioEventLoop,二者有异曲同工之妙!NioEventLoop可以查看《Netty源码阅读(一)-核心组件》。
Acceptor
Acceptor 是负责接收socket的线程,默认只有1个。
Acceptor 通过serverSocket.accept()方式,获得SocketChannel对象,将其封装在一个tomcat的实现类NioChannel对象中。然后将NioChannel对象封装在一个PollerEvent对象中,并将PollerEvent对象压入events queue里,所以Acceptor是events queue的生产者
Poller
Poller是IO线程,Poller线程中维护了一个Selector对象,NIO就是基于Selector来完成逻辑的。在connector中并不止一个Selector,在socket的读写数据时,为了控制timeout也有一个Selector。可以先把Poller线程中维护的这个Selector标为主Selector。 Poller是NIO实现的主要线程。首先作为events queue的消费者,从queue中取出PollerEvent对象,然后将此对象中的channel以OP_READ事件注册到主Selector中,然后主Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Worker线程,然后将socket传递给Worker。整个过程是典型的NIO实现。
Worker
Worker线程拿到Poller传过来的socket后,将socket封装在SocketProcessor对象中。然后从Http11ConnectionHandler中取出Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑,跟BIO实现一样。在Worker线程中,会完成从socket中读取http request,解析成HttpServletRequest对象,分派到相应的servlet并完成逻辑,然后将response通过socket发回client。在从socket中读数据和往socket中写数据的过程,并没有像典型的非阻塞的NIO的那样,注册OP_READ或OP_WRITE事件到主Selector,而是直接通过socket完成读写,这时是阻塞完成的,但是在timeout控制上,使用了NIO的Selector机制,但是这个Selector并不是Poller线程维护的主Selector,而是BlockPoller线程中维护的Selector,称之为辅Selector。
具体代码层面的处理流程如下:
2、连接Http服务器-Connector和Servlet服务器-Engine的中间桥梁是适配器CoyoteAdapter,具体连接到Engine的代码片段如下:
CoyoteAdapter#service()
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
三、Servlet服务器-Engine
1、由第一个大图可知,Engine组件包含四个:Host,Context,Wrapper和Pipeline,其中Pipeline是连接他们的,而Service、Engine、Host,Context,Wrapper都可以是多个,如何确定请求有谁处理的呢?
a) 根据协议和端口号选定Service和Engine
b) 根据域名或IP地址选定Host
c) 根据URI选定Context/Web应用
比如http://localhost:8080/app/hello.do这个请求,通过http协议和8080端口可以选定Service和Engine,localhost可以选定Host,app可以选定Context,hello.do则是由SpringMvc的HandlerMaping指定到具体的Controller。
Server.xml的核心配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="app" docBase="" reloadable="true" />
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
2、Servlet又是在何时创建的呢?
当请求在Servlet容器中,通过Pipeline管道,并执行管道上若干个阀门一层一层的流转,最后来到StandardWrapperValve执行Invoke方法,就在此处实例化Servlet!
StandardWrapperValve#invoke()核心代码如下:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
*************
// 创建Servlet
// Allocate a servlet instance to process this request
servlet = wrapper.allocate();
// 创建过滤器链
// Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 执行过滤器,在此调用Servlet的service方法.
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
filterChain.doFilter(request.getRequest(),response.getResponse());
*************
}
接下来就是执行HttpServlet.service()
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 执行service方法
service(request, response);
}