当前位置: 首页>后端>正文

Tomcat:网络请求原理分析

一、Http请求过程总览

浏览器请求

http://localhost/test/index.jsp

Tomcat:网络请求原理分析,第1张
  1. 用户点击网页内容,请求被发送到本机端口8080,被在那里监听的Coyote HTTP/1.1 Connector获得。

  2. Connector将Request包装成ServletRequest给它所在的Service的Engine来处理,并等待Engine的回应。

  3. Engine获得请求localhost/test/index.jsp,匹配所有的虚拟主机Host。

  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机),名为localhost的Host获得请求/test/index.jsp,匹配它所拥有的所有的Context。Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)。

  5. path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet,对应于JspServlet类。

  6. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet()或doPost().执行业务逻辑、数据存储等程序。

  7. Context把执行完之后的HttpServletResponse对象返回给Host。

  8. Host把HttpServletResponse对象返回给Engine。

  9. Engine把HttpServletResponse对象返回Connector,Connector把ServletResponse对象封装成Response。

  10. Connector把Response返回给客户Browser。

二、具体组件及源码分析

2.1 外部网络请求

2.1.1 Connector

用户在浏览器中输入一个URL地址之后浏览器将发起一个Http的请求,通过网络传输数据,请求到我们的Tomcat服务器中,Tomcat使用Connector接收Socket请求数据并通过Coyote链接器封装底层网络通信,为Catalina容器提供了统一的接口。

Tomcat:网络请求原理分析,第2张

在Coyote中,Tomcat支持一下3种协议:

  1. HTTP/1.1协议:目前最常用的访问协议
  2. AJP协议:Apache提供的一种协议,用于和Apache HTTP Server集成。
  3. HTTP/2.0协议:下一代HTTP协议,自Tomcat8.5版本开始支持。

针对HTTP和AJP协议,Coyote又按照I/O方式分别提供了不同的方案。

  1. BIO:同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成,Tomcat早期网络请求的默认实现方式(自8.5版本开始,已经移除)
  2. NIO:同步非阻塞的I/O模型(当前Tomcat的默认实现)
  3. NIO2:异步非阻塞I/O模型
  4. APR:Apache可移植运行库。

2.1.2 NioEndpoint

我们以NioEndpoint为例,来看一下Tomcat是如何实现网络请求收发的。


/**
* Defaults to using HTTP/1.1 NIO implementation.
* 默认使用NIO实现的http/1.1协议
*/
public Connector() {
  this("HTTP/1.1");
}


public Connector(String protocol) {
    boolean apr = AprStatus.getUseAprConnector() && AprStatus.isInstanceCreated()
            && AprLifecycleListener.isAprAvailable();
    ProtocolHandler p = null;
    try {
        //根据指定的协议,创建不同的处理器
        p = ProtocolHandler.create(protocol, apr);
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    }
    //省略部分代码...
}

我们发现Tomcat默认使用HTTP/1.1协议创建Http11NioProtocol,它默认使用NioEndpoint

public Http11NioProtocol() {
    super(new NioEndpoint());
}

当Connector执行init()的同时运行了protocolHandler.init()最终运行了NioEndpoint.init(),最终就是启动了一个ServerSocket并绑定监听端口。

protected void initServerSocket() throws Exception {
  //省略部分代码
  serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
    serverSock.bind(addr, getAcceptCount());
}

当Connector执行start()方法时,最终执行了:NioEndpoint.startInternal()方法。该方法内部启动了一个Poller线程,一个Acceptor线程。其中,Acceptor用于监听客户端连接,并将Socket连接放入待处理事件缓存池。Poller循环从待处理的事件缓存队列中拿到请求交给线程池处理。Poller将Socket请求包装成为:SocketProcessor对象,执行processor.process()方法,最终调用:getAdapter().service(request, response);这里的getAdapter即CoyoteAdapter,最终找到容器中管道方法的第一个阀门方法,发起调用,此时,网络请求正式进入我们容器中。

// Calling the container 调用容器
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

TIPS 如果想要详细了解Tomcat接收请求的流程图可以查看官网提供的资料

http://tomcat.apache.org/tomcat-9.0-doc/architecture/requestProcess/request-process.png

2.2 容器内部请求路径匹配

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
        //将coyote链接器中的Request,Response转换为我们常用的HttpServletRequest,HttpServletResponse
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    //省略部分代码...

        // 匹配请求路径,将当前request请求到匹配到的servlet上
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            //check valves if we support async
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container 调用容器
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
     // 省略部分代码...
}

在Tomcat启动的时候 Service持有了一个Mapper对象,它借助MapperListener在应用启动的过程中,将Engine,Host,Context,Wrapper路径都搜集了起来。当一个请求过来的时候它会去当前 Service中去匹配,最终将请求执行到我们StandardWrapperValve。此时会创建ApplicationFilterChain过滤器链;通过调用过滤器内部方法internalDoFilter;最终调用:javax.servlet.http.HttpServlet.service()方法,请求到应用中!

三、本文小结

我们通过一个Http请求路径分析了Tomcat是使用Socket接收网络请求,使用Coyote转换我们的网络协议参数,包装为ServletRequest和ServletResponse,使用匹配请求路径的方式最终找到我们应用中的Servlet地址。
至此,可以说我们对Tomcat的主要功能基本上已经了解的七七八八了,后续我们将会陆续的分享一些工作中常用的小技巧。

程序员的核心竞争力其实还是技术,因此对技术还是要不断的学习,关注 “IT巅峰技术” 公众号 ,该公众号内容定位:中高级开发、架构师、中层管理人员等中高端岗位服务的,除了技术交流外还有很多架构思想和实战案例。

作者是 《 消息中间件 RocketMQ 技术内幕》 一书作者,同时也是 “RocketMQ 上海社区”联合创始人,曾就职于拼多多、德邦等公司,现任上市快递公司架构负责人,主要负责开发框架的搭建、中间件相关技术的二次开发和运维管理、混合云及基础服务平台的建设。


https://www.xamrdz.com/backend/32b1941397.html

相关文章: