天天看点

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

首发CSDN:徐同学呀,原创不易,转载请注明源链接。我是徐同学,用心输出高质量文章,希望对你有所帮助。

本篇基于Tomcat10.0.6。

文章目录

    • 一、前言
    • 二、Connector基本结构
    • 三、ProtocolHandler
      • 1、Endpoint通信端点
      • 2、Processor应用层协议解析
        • (1)ConnectionHandler创建合适的Processor
        • (2)协议升级
      • 3、配置方式
    • 四、Adapter
    • 五、请求处理完整流程
    • 六、要点回顾
    • 七、参考文献

一、前言

Tomcat是一个非常优秀的开源项目,深深被它匠心独具的架构理念所折服。技术的思维是互通的,哪有那么多的高深莫测,都是对现实世界的抽象。

Tomcat作为Web应用服务器,接收请求和处理请求是它的两个核心功能。接收请求这步需要考虑适配多种网络协议,主流的是

HTTP

协议,还有

WebSocket

协议、

AJP

协议等;处理请求就是找到对应的

Servlet

,执行业务代码。

为了解耦和扩展性,Tomcat 设计了两个核心组件来做这两件事情:

  • 连接器(

    Connector

    )负责对外交流,建立

    Socket

    连接,读取并解析网络字节流,生成

    Request

    Response

    对象并转发给容器。
  • 容器(

    Container

    )负责内部处理,加载和管理

    Servlet

    ,通过

    Response

    对象的输出流写入响应结果。
    Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

二、Connector基本结构

具体来看看

Connector

有哪些功能:

  • 监听网络端口。
  • 接受网络连接请求。
  • 读取网络请求字节流。
  • 根据具体应用层协议(

    HTTP/AJP

    )解析字节流,生成统一的

    Request

    Response

    对象。(需要注意,

    Connector

    为了兼容多种协议,没有耦合

    Servlet

    标准,所以这里生成的

    Request

    Response

    对象没有实现

    HttpServletRequest

    HttpServletResponse

    接口)。
  • Request

    Response

    对象转换为

    HttpServletRequest

    HttpServletResponse

    对象。
  • HttpServletRequest

    HttpServletResponse

    对象转发给

    Container

总结就是做了三件事:

  • 网络通信。
  • 应用层协议解析和封装。
  • Request

    Response

    对象转换和传递。

对应

Connector

中的三个组件就是:

Endpoint

Processor

Adapter

,其中

Endpoint

Processor

放在一起抽象成了

ProtocolHandler

协议组件。

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

三、ProtocolHandler

ProtocolHandler

负责传输层网络连接和应用层协议解析,由两个核心部件

Endpoint

Processor

具体做这两件事。

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

从实现类来看,Tomcat的连接器主要支持两种协议:

HTTP/1.1

协议和

AJP

协议,实则还支持

HTTP/2.0

协议,

HTTP/2.0

的处理与

HTTP/1.1

AJP

不同,采用一种升级协议的方式实现。

  • HTTP/1.1

    协议:大家最熟悉的协议了,绝大多数web应用采用的访问协议,Tomcat既是

    Servlet

    容器又是HTTP服务器,单独运行就可以对外提供服务,但是一般会搭配

    Nginx

    web服务器做反向代理和负载均衡。
  • HTTP/2.0

    协议:自Tomcat8.5开始支持,相较于

    HTTP/1.1

    采用二进制传输数据而非文本格式;对消息头采用

    HPACK

    压缩,提升传输效率;基于帧和流的多路复用,真正实现了基于一个连接多请求并发处理;支持服务器主动推送。
  • AJP/1.3

    协议:全名Apache JServ Protocol,是Alexei Kosut创建的定向包通信协议,采用二进制格式传输可读文本。用于集成Web服务器,以提升静态资源的访问性能,当前最新版本为1.3。目前

    Apache

    Tomcat

    Nginx

    Jetty

    JBoss

    等均已支持

    AJP

    。(名不见经传,不过多解释)

除了支持3种协议外,还分别支持3种I/O方式:

NIO

NIO2

APR

,Tomcat8.5之前默认还支持

BIO

,后来因为性能问题直接给删除了,APR也贴上了过期标签。协议和I/O方式两两组合就出现了很多实现类:

Http11NioProtocol

Http11Nio2Protocol

Http11AprProtocol

(已过期)、

AjpNioProtocol

AjpNio2Protocol

AjpAjpProtocol

(已过期)。(

HTTP/2.0

是在

HTTP/1.1

的基础上升级处理的,不在该继承体系内,

APR

采用

Apache

可移植运行库(

c++

编写的本地库,就是

native

方法)实现的,官方已标注过期不建议使用,所以不过多解释)

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

Tomcat采用

UpgradeProtocol

表示HTTP升级协议,当前只有一个实现类

Http2Protocol

用于处理

HTTP/2.0

。它根据请求创建一个用于升级处理的令牌

UpgradeToken

,该令牌包含了具体的HTTP升级处理器

HttpUpgradeHandler

HTTP/2.0

的处理器为

Http2UpgradeHandler

)。(Tomcat中

WebSocket

也是通过

UpgradeToken

机制实现的,其处理器为

WsHttpUpgradeHandler

1、Endpoint通信端点

Endpoint

负责网络通信,监听一个端口,循环接收

socket

请求,读取网络字节流等。

Endpoint

不是接口,而是提供了一个抽象类

AbstractEndpoint

,又根据I/O方式提供了若干实现类:

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?
Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

网络通信这一层是非常抽象的,传输层协议毋庸置疑是

TCP/IP

,但是如何监听socket请求,读取网络字节流的方式是多样的,同步非阻塞、I/O多路复用、异步非阻塞等等:

  • Endpoint

    高度抽象出一个

    Acceptor

    来循环监听

    socket

    请求;
  • Tomcat是支持高并发的,但是机器的性能是有限的,为了保证Web服务器不被高流量冲垮,所以在接收请求前会有一个

    LimitLatch

    限流器(利用

    AQS

    实现);
  • 接收到的

    socket

    通道(

    SocketChannel

    or

    AsynchronousSocketChannel

    ),会先根据I/O方式包装一下,如同步非阻塞

    NioChannel

    、异步非阻塞

    Nio2Channel

    等;
  • 包装之后的

    socket

    通道又用一个更抽象的

    SocketWrapper

    封装,以应对不同方式的网络字节流的读取和写入;
  • 出于高并发的设计,将抽象的

    SocketWrapper

    交由

    SocketProcessor

    任务对象处理,

    SocketProcessor

    会扔进一个线程池

    Executor

    处理;
  • 为了进一步提高性能,每次处理请求都会把一些对象缓存起来,不重复创建,比如

    SocketWrapper

    SocketProcessor

    等。
Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

2、Processor应用层协议解析

Acceptor

接收到请求封装成一个

SocketProcessor

扔进线程池

Executor

后,会调用

Processor

从操作系统底层读取、过滤字节流,对应用层协议(

HTTP/AJP

)进行解析封装,生成

org.apache.coyote.Request

org.apache.coyote.Response

对象。不同的协议有不同的

Processor

HTTP/1.1

对应

Http11Processor

AJP

对应

AjpProcessor

HTTP/1.2

对应

StreamProcessor

UpgradeProcessorInternal

UpgradeProcessorExternal

用于协议升级:

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

(1)ConnectionHandler创建合适的Processor

SocketProcessor

并不是直接调用的

Processor

,而是通过

org.apache.coyote.AbstractProtocol.ConnectionHandler#process

找到一个合适的

Processor

进行请求处理:

  • 根据不同协议创建

    Http11Processor

    or

    AjpProcessor

  • 根据协议升级是内部升级(

    HTTP/2.0

    )还是外部升级创建

    UpgradeProcessorInternal

    or

    UpgradeProcessorExternal

(2)协议升级

如果是正常的协议,如

HTTP/1.1

AJP/1.3

,则

Processor#process

处理完请求后会直接调用

Adapter#service

,将请求转发给

Container

如果是协议升级(除

Websocket

),首先通过HTTP/1.1进行协议升级:

  • 服务器接收到带有特殊请求头(

    Upgrade

    )的

    HTPP/1.1

    连接,因此仍会先交给

    Http11Processor

    进行处理;
  • 根据请求头

    Upgrade

    对应协议名创建

    UpgradeToken

    ,并赋值给当前

    Processor

  • 返回

    SocketState.UPGRADING

    ,再由

    ConnectionHandler

    进行协议升级;
  • ConnectionHandler

    会从当前

    Processor

    获取

    UpgradeToken

    对象(如果没有,则默认为HTTP/2.0),并构建一个升级的

    Processer

    (若为Tomcat可以处理的协议升级(

    HTTP/2.0

    WebSocket

    ) ,则是

    UpgradeProcessorInternal

    ,否则为

    UpgradeProcessorExternal

    )。
  • 替换当前

    Processer

    ,并将当前

    Processer

    释放回收;
  • UpgradeProcessor

    设置给

    UpgradeToken

    中的

    HttpUpgradeHandler

    ,并调用

    HttpUpgradeHandler.init

    进行初始化,开启升级协议的处理。
  • 由于

    HTTP/2.0

    是多路复用协议,一个连接可以处理多个HTTP请求,所以对于

    Http2UpgradeHandler

    ,会将每次请求响应交于

    StreamProcessor

    处理,再由

    StreamProcessor

    将请求提交给

    Container

注意:

  • UpgradeProcessorInternal

    UpgradeProcessorExternal

    都实现了接口

    WebConnection

    ,表示一个用于升级的连接,并不处理协议升级后数据读写和解析,而是交由

    HttpUpgradeHandler

    ,对于

    Http2UpgradeHandler

    再构建

    StreamProcessor

    ,又将

    StreamProcessor

    包装成任务类

    StreamRunnable

    ,扔进线程池

    Executor

    处理。
  • WebSocket

    也属于协议升级,和

    HTTP/2.0

    升级方案一致,但是协议升级的判断机制有所不同,

    WebSocket

    升级判断不在连接器里,而是交由

    Servlet

    容器通过当前请求的过滤器

    WsFilter

    判断,如果是

    WebSocket

    协议升级,则调用当前

    org.apache.catalina.connector.Request#upgrade

    构建

    UpgradeToken

    并传递给

    Http11Processor

    处理(调用钩子函数

    org.apache.coyote.AbstractProcessor#action

    ),之后到了

    ConnectionHandler

    逻辑就跟

    HTTP/2.0

    升级差不多了。

3、配置方式

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000" 
           executor="tomcatThreadPool" 
           redirectPort="8443">
     <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/>
</Connector>
           
  • port

    Connector

    监听的端口。
  • protocol

    是应用层协议名,可填参数有

    HTTP/1.1

    org.apache.coyote.http11.Http11NioProtocol

    AJP/1.3

    org.apache.coyote.ajp.AjpNioProtocol

    ,如果

    protocol

    不填,则默认为

    Http11NioProtocol

  • connectionTimeout

    表示

    Connector

    接收到连接后等待超时时间,单位毫秒,默认20秒。
  • executor

    表示使用一个共享线程池,若使用私有线程池,则

    executor

    不需要指定,私有线程池可选参数有

    minSpareThreads=“10”

    maxThreads=“200”

  • redirectPort

    表示非

    SSL

    重定向到

    SSL

    端口,当请求是

    non-SSL

    请求,但是接收到的请求内容需要

    SSL

    传输,则重定向到

    SSL

    端口。
  • 若为

    HTTP

    开启

    HTTP/2.0

    支持,需要配置

    UpgradeProtocol

    Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

四、Adapter

Adapter

接口只有一个实现类

org.apache.catalina.connector.CoyoteAdapter

,其主要职责如下:

  • org.apache.coyote.Request

    org.apache.coyote.Response

    转为实现了标准

    Servlet

    org.apache.catalina.connector.Request

    org.apache.catalina.connector.Response

  • 将请求体的

    serverName

    URI

    version

    传给

    Mapper

    组件做映射,匹配到合适的

    Host

    Context

    Wrapper

  • Request

    Response

    传给

    Container

    处理,

    Engine

    通过管道

    Pipeline

    传给

    Host

    Host

    再传给

    Context

    Context

    再传给

    Wrapper

    Wrapper

    是最终的

    Servlet

五、请求处理完整流程

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

六、要点回顾

任重而道远啊,本篇主要从

Connector

的整体架构进行讲解,主要弄明白

Connector

是如何设计的?为什么要这样设计?

Connector

作为独立模块,封装底层网络通信,使

Container

和具体的协议及I/O方式解耦,易扩展、高性能。这也使得架构变得抽象复杂,

Connector

需要应对多种协议和I/O方式的组合,高度的抽象和封装。

Connector

只负责接收和解析请求,具体的业务处理还需要交给

Container

,所以需要一个适配器作为

Connector

Container

连接的桥梁。

很多实现细节并没有展开,比如:

  1. Endpoint

    的工作原理没有展开,如何监听请求?如何限流?如何封装接收到的

    socket

    ?如何将连接扔进线程池里处理?
  2. Processor

    应用层协议解析原理没有展开,如何读取底层字节流?如何解析封装应用层协议?如何封装生成

    Request

    Response

  3. 适配器

    Adapter

    如何做转发?如何找到对应的

    Host

    Context

    Wrapper

涉及的知识点很多,比如:

  1. 对传输层协议

    TCP/IP

    的理解。
  2. 对I/O模型的理解(I/O多路复用、同步非阻塞、异步非阻塞等)。
  3. 对传输层协议的理解(HTTP协议真的要掌握,不然真看不懂代码是怎么解析请求行、请求头、请求体的)。
  4. 高并发架构设计(缓存、

    Reactor

    响应式、传输层协议延迟加载)。

后续会一一讲解。

Tomcat连接器Connector源码解读(一)架构概览,如何设计?为什么这样设计?

七、参考文献

  1. 书籍:《Tomcat架构解析》刘光瑞(Tomcat8.5)
  2. 书籍:《Tomcat内核设计剖析》汪建(Tomcat7)
  3. 极客时间:《深入拆解Tomcat & Jetty》李号双(Tomcat9.x)
  4. Tomcat源码:https://gitee.com/stefanpy/tomcat-source-code-learning (Tomcat10.0.6)

如若文章有错误理解,欢迎批评指正,同时非常期待你的留言和点赞。如果觉得有用,不妨点个在看,让更多人受益。

继续阅读