3

Tomcat请求处理流程与源码浅析 - Cuzzz

 10 months ago
source link: https://www.cnblogs.com/cuzzz/p/17438563.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

系列文章目录和关于我

image-20230507214918965

一丶Connector#

在tomcat中,Connector负责开启socket并且监听客户端请求,返回响应数据。

image-20230521211759461
  • Endpoint:tomcat中没有这个接口,只有AbstractEndpoint,它负责启动线程来监听服务器端口,并且在接受到数据后交给Processor处理
  • Processor:Processor读取到客户端请求后按照请求地址映射到具体的容器进行处理,这个过程请求映射,Processor实现请求映射依赖于Mapper对象,在容器发生注册和注销的时候,MapperListener会监听到对应的事件,从而来变更Mapper中维护的请求映射信息。
  • ProtocolHandler:协议处理器,针对不同的IO方式(NIO,BIO等)和不同的协议(Http,AJP)具备不同的实现,ProtocolHandler包含一个Endpoint来开启端口监听,并且包含一个Processor用于按照协议读取数据并将请求交给容器处理。
  • Acceptor:Acceptor实现了Runnable接口,可以作为一个线程启动,使用Socket API监听指定端口,用于接收用户请求。
  • Poller:主要用于监测注册在原始 scoket 上的事件是否发生,Acceptor接受到请求后,会注册到Poller的队列中。

二丶NioEndpoint 初始化ServerSocketChannel#

springboot内嵌tomcat,一般默认使用NioEndpoint,在NioEndpoint#start方法中,会触发NioEndpoint#bind

image-20230521214539181

三丶NioEndpoint 启动Poller和Acceptor线程#

NioEndpoint#start方法最后会触发Poller线程和Acceptor线程的启动

image-20230521215101571

image-20230521215243031

可以看到NioEndpoint内部的Poller,和Acceptor都是单独使用一个守护线程来运行。

四丶Acceptor接收请求#

image-20230521220055751

1.endpoint.countUpOrAwaitConnection()限制连接数#

其内部使用LimitLatch#countUpOrAwait方法限制连接数,如果连接数达到了上限,那将挂起当前线程,也就是挂起Acceptor线程,从而导致无法有更多的请求连接上来,最大连接数默认为8*1024。

LimitLatch 内部持有一个AbstractQueuedSynchronizer,限制连接数将调用其acquireSharedInterruptibly(1),然后会调用到AQS的tryAcquireShared,其内部使用AtomicLong来进行连接的计数。

image-20230521221136584

2.NioEndpoint#serverSocketAccept 接收Socket连接#

image-20230521221800829

由于NioEndpoint前面调用了ServerSocketChannel#configureBlocking(true),所以serverSock#accept,在没有连接上来时,不会立马返回null,而是阻塞直到连接来到。

3.NioEndpoint#setSocketOptions将SocketChannel注册到Poller#

image-20230521224050992

在Acceptor线程接收到SocketChannel后,会调用Poller#register方法进行注册,Acceptor只负责接受请求,请求后续的处理由Poller线程负责

image-20230521224257316

image-20230521224401100

最终请求被包装为PollerEvent丢到Poller的事件队列SynchronizedQueue中,SynchronizedQueue使用synchronized保证线程安全。

wakeupCounter 是AtomicLong类型,Acceptor接受到请求,将请求封装为PollerEvent后会调用wakeupCounter#incrementAndGet方法,进行+1操作

Poller在使用Selector,进行IO多路复用的时候,会进行如下操作

image-20230521225002993

可以看到,如果wakeupCounter大于0,Poller会调用 selector.selectNow()(非阻塞立马返回),反之调用selector.select(selectorTimeout)(超时并阻塞)。

也就说Acceptor接受到请求越多,wakeupCounter越大,越会让Poller调用selector.selectNow()减少阻塞,从而让Poller更快的检查事件是否就绪,从而让请求更及时的被处理。

五丶Poller处理事件#

1.events方法查看事件队列是否具备事件#

上面我们说到Acceptor在建立连接后,将SocketChannel包装成NioSocketWrapper塞到了Poller的事件队列中。而Poller线程则会一直轮询这个队列进行事件的获取

image-20230528152708481

2.Poller 使用Selector进行select#

image-20230528153510951

通过Selector获取获取当前就绪的IO,keyCount记录就绪数目。

3.Poller 处理就绪IO#

image-20230528154651828

processKey会调用到processSocket,最终使用tomcat线程池中的线程进行异步处理

image-20230528155459420

最终会找到Processor进行处理(默认使用缓存的,避免重复new对象,频繁gc,如果缓存没有那么使用ProtocolHandler 创建出一个),这里的Processor就是Http11Processor

然后根据事件类型进行不同的处理,如果是读事件那么会调用Http11Processor#service进行处理,然后会继续交给CoyoteAdapter调用其service进行处理。

六丶CoyoteAdapter处理请求#

1.使用Mapper找到请求对应的Host,Context,Wrapper#

image-20230528164457609

下图是的模型,如果使用了SpringMVC,这里的Wrapper会存在DispatchServlet

image-20230515003153142

如下是Mapper找到的MappingData

image-20230528164322639

2.Pipeline执行#

image-20230528170245899

上面说到,Mapper会找到当前请求所属的host,context和对应的Wrapper,紧接着会进行Pipeline的执行。

为了增强扩展性,tomcat定义了Pipeline(管道)和Valve(阀),Pipeline使用职责链的方式串联多个Valve——来自客户端的请求如同流水一样流淌在管道中,受到每一个阀的作用。

image-20230507184619665

Pipeline中维护了基础的Valve,始终位于Pipeline末端,通过Pipeline#addValve添加的Valve违约基础的Valve之前。

在Tomcat中Engine,Host,Context,Wrapper都有对应的Valve实现,同时维护了一个Pipeline,从而让我们可以对请求的处理进行扩展。

下面是比较重要的Valve

  • StandardEngineValve :Engine对应的Valve,负责请求是否通过mapper找到了对应的Host,并触发Host对应的Valve

    image-20230528170655504
  • ErrorReportValve: 错误报告Valve让后续的Valve继续执行,如果执行出现错误那么会刷新响应流,让客户端收到响应

    image-20230528171000836
  • StandardHostValve:Host对应的Valve,如果请求没有匹配的context返回404,反之调用Context对应的Valve

    image-20230528171220823
  • StandardContextValve:Context对应的Valve,如果请求路径以/META-INF/,或者/WEB-INF/开头,会直接返回404,反之继续调用Wrapper对应的

  • StandardWrapperValve:Wrapper对应的Valve,会负责组装Servlet和Filter,并执行FilterChain#doFilter方法

    image-20230528171752416

    Filter的匹配主要通过DispatchType和Filter设置的路径,

    image-20230528172344294

    在SpringBoot项目中可以使用FilterRegistrationBean#setDispatcherTypes,和addUrlPatterns进行指定。

3.FilterChain执行#

在Tomcat中ApplicationFilterChain实现了Java Servlet规范中的FilterChain。

image-20230528172912949

其中使用ApplicationFilterConfig是对FilterConfig的实现,内部持有一个Filter。

ApplicationFilterChain包含多个ApplicationFilterConfig,使用数组和pos属性记录当前执行到第几个Filter

image-20230528173109162

Filter都执行结束后,将执行Servlet#service方法

在SpringMVC项目中,会调用到DispatcherServlet#service,最终调用到Controller。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK