Tomcat源码阅读(二)-请求处理流程

一、请求处理流程图

在这里插入图片描述
上图中有两个重要的组件: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 &quot;%r&quot; %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);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tomcat源码解析是对Tomcat服务器的源代码进行分析和解读的过程。通过对Tomcat源码的研究,可以深入了解Tomcat的整体架构、连接器的内部结构、容器分析以及Tomcat的启动流程等方面的内容。 Tomcat的整体架构包括配置文件server.xml的分析和连接器的内部结构。配置文件server.xml是Tomcat的主要配置文件,通过对其进行分析可以了解Tomcat的各个组件和配置项的作用。连接器是Tomcat的核心组件之一,负责处理客户端请求并将其转发给相应的容器进行处理Tomcat的启动流程是通过实现Lifecycle接口的各个组件来完成的。在启动过程中,Tomcat会按照一定的顺序初始化和启动各个组件,确保它们能够正常工作。具体的启动流程可以通过阅读源码中的相关方法和注释来了解。 Tomcat底层使用了Netty来实现IO相关的操作,但与Netty有所区别,因为Tomcat对部分处理进行了封装。通过对Tomcat源码的学习,可以了解Tomcat底层的实现逻辑、各个组件的配合方式以及各种设计模式的交互。 如果你对Tomcat源码解析感兴趣,可以参考提供的源码和相关文章进行深入研究。通过深入研究Tomcat源码,你可以更好地理解Tomcat的工作原理和内部机制。 #### 引用[.reference_title] - *1* [Tomcat源码分析](https://blog.csdn.net/sun_code/article/details/123554480)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [tomcat线程模型-源码解析](https://blog.csdn.net/qq_16498553/article/details/126080174)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值