浏览器发送http请求->建立Socket连接->通过Socket读取数据->根据http协议解析数据->调用后台服务完成响应。
Tomcat既是一个HttpServer,也是一个Servlet 容器,首先根据HTTP协议规范解析请求数据,然后将请求转发给Servlet进行处理。 当用户请求服务器的时候,Connector会接受请求,从Socket连接中根据http协议解析出对应的数据,CoyoteAdapter构造Request和Response对象,然后传递给后面的顶层容器StandardEngine处理,StandardEngine处理请求其实是通过容器的Pipeline进行的,而Pipeline其实最终是通过管道上的各个阀门进行的,当请求到达StandardEngineValve的时候,此阀门会将请求转发给对应StandardHost的Pipeline的第一个阀门处理,然后以此最终到达StandardHostValve阀门,它又会将请求转发给StandardContext的Pipeline的第一个阀门,这样以此类推,最后到达StandardWrapperValve,此阀门会根据Request来构建对应的Servelt,并将请求转发给对应的HttpServlet处理。其实Tomcat核心处理流程就是通过责任链一步步的组装起来的。
Service可以同时有多个Connector对象
- 当Tomcat启动后,Connector组件的接收器(Acceptor)将会监听是否有客户套接字连接并接收Socket。
- 一旦监听到客户端连接,则将连接交由线程池Executor处理,开始执行请求响应任务。
- Http11Processor组件负责从客户端连接中读取消息报文,然后开始解析HTTP的请求行、请求头部、请求体。将解析后的报文封装成Request对象,方便后面处理时通过Request对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
- Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配由哪个Host容器、哪个Context容器、哪个Wrapper容器处理请求,这个过程其实就是根据请求从Tomcat中找到对应的Servlet,然后将路由的结果封装到Request对象中,方便后面处理时通过Request对象选择容器。
- CoyoteAdapter组件负责将Connector组件和Engine容器连接起来,把前面处理过程中生成的请求对象Request和响应对象Response传递到Engine容器,调用它的管道。
- Engine容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些处理逻辑。这里xxxValve代表某阀门,我们可以根据自己的需要往这个管道中添加多个阀门,首先执行这个xxxValve,然后才执行基础阀门EngineValve,它会负责调用Host容器的管道。
- Host容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve,它继续往下调用Context容器的管道。
- Context容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve,它负责调用Wrapper容器的管道。
- Wrapper容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve,它会执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。
连接器Connector是Tomcat的连接器,主要的目的是监听外围网络访问请求,而连接器在启动相关监听进程后,是通过NIO方式进行请求的监听-响应-处理。
public AbstractAjpProtocol(AbstractEndpoint<S,?> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
// AJP does not use Send File
getEndpoint().setUseSendfile(false);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}复制代码
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}复制代码
ProtocolHandler接口是所有protocol类的顶层接口。Protocol根据请求协议分为HTTP和AJP(TCP/IP),共有六个具体实现类,每种协议都能选择三种endpoint(nio、nio2、apr)。
每个protocol的生命周期操作(init、start、pause、resume、stop、destroy)是定义在他们
的公共顶层抽象类AbstractProtocol中,所以每一个protocol的生命周期运行方式是一样的。
AJP(Apache JServ Protocol)是定向包协议,因为性能原因,使用二进制格式来传输可读性文本。
AJP可用于Tomcat的负载均衡,例如Nginx可以通过AJP协议向tomcat发送请求:
AJP优点:
- 发送的内容都是高度压缩的,所以消耗流量很少
- WEB服务器和SERVLET容器建立的是持久性tcp连接
- 性能比http更好(cpu使用率更低,时间略微更快)
AJP缺点:
- 由于是持久连接,可能会导致连接数过多
每种ProtocolHandler包含3个重要组件来处理请求:
- Adapter:用于将封装好的Request交给Container,将请求适配到servlet容器。请求响应对象从连接器传送到容器需要一个桥梁CoyoteAdapter 。
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);}复制代码
- Endpoint:用于处理底层Socket连接(nio和nio2实现的是TCP/IP协议,Apr实现的是SSL/TLS协议)
- Processer:用于将Endpoint接收到的Socket封装成Request(实现HTTP协议或websocket协议或AJP协议)
Apr(Apache portable Run-time libraries)简单理解就是从操作系统级别解决异步IO问题,大幅度的提高服务器的处理和响应性能, 也是Tomcat运行高并发应用的首选模式。需要额外安装apr和native。使用了tomcat native技术之后,tomcat在跟操作系统级别的交互方面可以做得更好。apr中socket的接收实际上是调用的native。 Tomcat-native是tomcat的一个子项目,一部分是用java写的,一部分是c写的。 Tomcat-native可以看做是一个集成包,里面集成了两个东西:openssl,这个是ssl信道的实现,另外一个是高性能的apr网络库(都是c写的)。
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// Start poller threads
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
startAcceptorThreads();
}
}复制代码
"http-nio-8080-Acceptor-0" #30 daemon prio=5 os_prio=0 tid=0x0000000058ee4000 nid=0x1b54 runnable [0x0000000057c8f000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) - locked <0x00000000e264e6c0> (a java.lang.Object)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:417)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:69)
at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"ajp-nio-8009-Acceptor-0" #44 daemon prio=5 os_prio=0 tid=0x0000000059402000 nid=0x1aac runnable [0x000000005d69f000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250) - locked <0x00000000e26a6db8> (a java.lang.Object)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:417)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:69)
at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
Poller实现了Runnable接口,Socket内容的读写是通过Poller来实现的。NioEndpoint.startInternal(),初始化pollers数组,启动pollers数组中的线程,让pollers开始工作。
Poller默认两个线程,扫描PollEvent队列,进行处理,启动业务处理线程SocketProcessor循环扫描,PollEvent队列中是否存在待处理事件。从SelectionKey中获取NioSocketWrapper对象((NioSocketWrapper)sk.attachment())将NioSockectWrapper对象封装成SocketProcessor(从缓存中读取,没有就新生成)线程池启动SocketProcessor线程处理SocketProcessor线程调用ConnectionHandler进行处理,ConnectionHandler获取一个Processor进行处理(Processor也存在一个对象堆缓存,Stack实现)
"http-nio-8080-ClientPoller-0" #28 daemon prio=5 os_prio=0 tid=0x0000000058ee2800 nid=0x16ac runnable [0x000000005bf9e000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000000e2652948> (a sun.nio.ch.Util$3)
- locked <0x00000000e2652938> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000000e26527c8> (a sun.nio.ch.WindowsSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:702)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
类图:www.processon.com/view/link/5…
启动停止:www.processon.com/view/link/5…