tomcat是一款基于servlet规范开发的应用服务器。(http服务器+servlet容器)
概述
整体架构包含两个核心组建 连接器和容器。连接器对外 处理连接,容器对内处理业务请求
每个service里面有多个连接器和一个容器
连接器使用ProtocalHandler接口封装通信协议和IO模型的差异
连接器
主要有三块
【Endpoint】底层socket维护,维护连接,解析tcp ip协议 把字节流送给processor
【Processor】应用层协议解析器 解析http/ajp/之类,把请求处理成TomcatRequest传给adapter
【Adapter】连接器和容器的桥梁,把tomcatRequest转换成servletRequest
ProtocalHandler包含endpoint和processor两个模块,就是把socket处理成tomcatRequest屏蔽底层网络连接和应用层协议的解析
![](https://img-blog.csdnimg.cn/2020080518100537.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM2NTc5OTM=,size_16,color_FFFFFF,t_70)
NIO2 类似就是没有了Poller,这个遍历可读时间交给内核了,自己注册回调函数 直接用数据就好了
APR是C++写的直接用了操作系统的底层命令,多路复用用的是操作系统里面的epoll命令
- 通过DirectByteBuffer避免了JVM堆内存和本地内存的拷贝
- 利用操作系统的sendFile减少了用户态和内核态的切换 减少了用户内存和内核缓冲区数据的拷贝
tomcat支持的最大连接数
说下结论,tomcat能接受的最大连接数 maxConnections + acceptCount
acceptCount是存放建立好的长连接,tcp握手阶段 完成三次握手会把tcp丢到这个队列 长度可以简单理解成是accepCount
如果maxConnections满了 不会再去accpetCount这个队列里面取连接了
maxConnection是LimitLatch控制的【limitLatch也是基于aqs实现的,有兴趣可以看我对aqs的介绍 】
- BIO默认最大连接是200
- NIO默认最大连接是1W
- APR默认连接是8192
<Executor
name="tomcatThreadPool"
namePrefix="deppyu-exec-"
//处理连接的最大请求数-BIO模式下 就是最大连接数
maxThreads="4000"
//初始化启动的线程数
minSpareThreads="1000"
//超过这个线程数 会关闭空闲线程
maxSpareThreads="2000"
maxIdleTime="60000"
/>
<Connector
port="80"
maxHttpHeaderSize="8192"
enableLookups="false"
redirectPort="8443"
//保持建立好的tcp连接 太小了容易 connect reset 太大了就容易长时间等待
acceptCount="2000"
connectionTimeout="20000"
disableUploadTimeout="true"
//NIO一般用这个参数
maxConnections=10000
executor="tomcatThreadPool"
/>
容器
tomcat的容器主要分四个组建,基本都是1对N的关系。
host域名/context代表一个web服务/wrapper就是servlet。这几个使用的是组合模式,都有一个父接口Container。
来简单看下外部请求是如何定位到具体的servlet的。
请求过来一定是先建立连接的,这个时候Connector就被确认了,connector属于一个service,一个service里面只有一个容器。
现在容器也确定了,后面按照 url context就能找到context,再根据conetxt的web.xml按照映射配置找到servlet
tomcat server.xml片段
<Server>
<Service>
<Connector>
</Connector>
<Engine>
<host>
<Context></Context>
</host>
</Engine>
</Service>
</Server>
web.xml片段
<!--****************************servlet映射关系配置*************************--> <servlet>
<servlet-name>XxServlet</servlet-name>
<servlet-class>com.xx.XxServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XxServlet</servlet-name>
<url-pattern>/xx</url-pattern>
</servlet-mapping>
![](https://img-blog.csdnimg.cn/20200624095738247.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM2NTc5OTM=,size_16,color_FFFFFF,t_70)
启动脚本看启动流程
里面用的是组合模式 catalina调用server的start方法,server调用service的start。
service调用连接器和Engine的start,他们都是Container的实现,也都实现了LifeCycle接口
tomcat实现servlet规范
如上面的图-请求流程里面的结构。每个context里面有多个servlet,但不是直接持有servlet的。而是支持持有的wrapper
wrapper里面有servlet实例,servlet的处理范围(就是映射的url),servlet的初始化参数。
实例化servlet
是调用loadServlet方法,主要两步
step1.实例化servlet
step2.调用servlet的init方法
loadServlet的调用时机 一般类似懒加载,只有对应请求来了 才会调用。(但是wrapper对象一定会实例化的)
当然如果初始化参数loadOnstartUp=true,就会直接加载初始化
调用servlet的service方法
上面这张图很容易看出,warpper的责任链默认里面的basic【StandrandWrapperValva】节点会调用servlet
这个warpper里面的invoke主要做了以下几件事
- 获取servlet实例(单例的)
- 组建filiterChain
- 调用doFilter
看下filterChain的代码,也是一种责任链的写法。传递filterChain自身,每个filter调用chain的doFilter传递。
最后一个fliter执行完了,会调用servlet.service()
public final class ApplicationFilterChain implements FilterChain {
//Filter链中有Filter数组,这个好理解
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
//Filter链中的当前的调用位置
private int pos = 0;
//总共有多少了Filter
private int n = 0;
//每个Filter链对应一个Servlet,也就是它要调用的Servlet
private Servlet servlet = null;
public void doFilter(ServletRequest req, ServletResponse res) {
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest req, ServletResponse res){
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
return;
}
//如果filter结束了 调用servlet
servlet.service(request, response);
}
tomcat