整体架构
简介
Server是顶层容器,代表整个服务器,包含多个Service
每个Service包含**多个Connector**和**一个Container**,对外提供服务
每个Connector负责连接相关的事情,提供socket与Request和Response相关转换
Container用于封装和管理Servlet,以及具体处理Request请求
架构关系
Connector
Endpoint
监听socket通道,将socket接收到的数据转发给Processor
Processor
接收并解析来自Endpoint的http请求,封装成Request转发给Adapter
Adapter
接受来自Processor的Request对象,并把Request对象封装成ServletRequest,转发给Container,这里用到了**门面模式**
Container
Engine
一个Service只有一个Engine
Host
一个Engine可以有很多个虚拟主机(Host)
Context
每个Host又可以有很多个web程序(Context)
Wrapper
每个Context又对应很多个Servlet(Wrapper),Wrapper作为最底层的容器,不再有子容器
启动流程
点击startup.bat启动tomcat,这个批处理命令会去找catalina.bat
在catalina.bat中做了一件事,就是去找Bootstrap的main方法
那为什么要一个文件一个文件找呢?为什么不直接打开Bootstrap的main方法呢?
其实我抽取出来的重点是找到Bootstrap的main方法,但是中途有很多的系统验证,比如是否安装jdk等等,其中涉及的变量还是很多的
进入到BootStrap的main方法后,首先加上一个同步锁,再执行init初始化方法<br>
init方法中,通过反射创建Catalina实例,并设置成守护线程
然后执行load加载方法,这个load方法执行完后,从server、service一直到context都被初始化完成了,这里是个嵌套的关系<br>
执行完load方法后,执行start方法,再从server、service到ProtocolHandler都执行start方法,和上面一样,是嵌套关系<br>
这里面同时还涉及到一个线程池的创建和初始化,这个操作紧跟在Service后面,Engine前面<br>
请求流程
Connector阶段
Endpoint的NIOEndpointAcceptor监听socket端口,接受到请求,将请求转发给Processor<br>
Processor将接受到的socket请求封装成Request对象<br>
因为Container接受的是HttpRequest,而不是Request,所以adaptor的实现类coyoteAdapter会把Quest对象封装成HttpRequest,转交给Comtainer,这里设计到<font color="#e65100"><b>门面模式</b></font>,其实就是进行了对象封装<br>
这个接受请求转发给Container的整体实现有两种,一种是基于ajp实现的potocolHandler,一种是基于http1.1实现的protocolHander
Container阶段
Engine、Host、Context、Warpper都有标准的实现类,名字叫做StandardXXX
在请求传递过程中,会出现一个管道机制,它由pipeline和valve组成
这个其实就相当于一个拦截器,请求在每层容器传递过程中都要进行一些该层容器的处理,再交给下层的子级容器
管道通常由一个基础阀和若干普通阀组成,基础阀就是在该级容器和下层容器间建设桥梁,起到传递的作用,而普通阀就是用来做逻辑处理的
这里涉及到了责任链模式,在每层容器中都可以通过getPipeline得到管道对象,再由getFirst().invoke()执行第一个阀门逻辑,然后通过getNext().invoke()执行下个阀门逻辑<br>
最终会到达相对于的servlet执行具体的doGet、doPost方法,然后返回结果
常见问题
为什么使用线程池
如果没有线程池的话,每个请求到来的时候都会去开启一个线程对请求进行处理,若果一瞬间打来了上千个请求,会瞬间开启很多线程,直接就会导致服务器宕机,但是线程池在请求过多的时候会把暂时无法处理的请求存储在等待队列中,再者,线程池还有饱和策略应对请求过多的情况
另外就算请求量没有很高,每个请求来了还是得申请线程处理,线程线程完毕后立即销毁,不断的请求打过来,就会不断的重复这个过程,会给系统带来巨大的性能开销,我看过linux对于线程销毁的处理,线程的销毁并不是立刻销毁的,而是批量回收的,这个回收的过程会占用cpu资源,影响其他线程的运行,所以我们应该减少线程的销毁这一操作,线程池的线程的run方法是死循环的,它会不断的从队列中取任务执行,而不被销毁,这就减少了线程销毁这一操作带来的性能消耗,大幅提高了多线的的性能
void类型的doGet()和doPost()怎么返回数据<br>
我们发起http请求,tomcat就接受到请求会封装两个对象,一个是request,一个是response
request就是我们的请求,会转发给相对应的servlet,然后处理的结果会保存在respoonse中,由tomcat把response转换成流,再通过tomcat返回给前端