tomcat请求流程分析
一、tomcat组成
tomcat架构tomcat中有一个Engine会包含多个Host(类似List<Host>),请求从Engine到Host会流经Pipeline,Pipeline中有多个Valve,Valve会处理一些事情;然后从Host到Context,再到Wrapper,最后才到Servlet;没到一级,中间就会经过Pipeline;怎么理解Pipeline呢,类似于一个Valve的集合,Valve是具体的处理方式,和过滤器差不多,我们也可以自定义,一般不会这么做,没必要在tomcat层去处理请求;
二、接受数据流程
1.以BIO的Http请求为例
协议配置当我们在server.xml中配置好协议后,根据全限定名去查找协议解析策略;
Http11Protocol当Http11Protocol初始化时;会初始化JIoEndpoint,JIoEndpoint中包含两个处理请求的内部线程,Acceptor和SocketProcessor;
Acceptor:接受Socket,建立连接,一个协议只有一个线程在跑,接收到连接后,交给SocketProcessor线程池,处理请求,SocketProcessor线程池的大小,决定了服务器的并发量;
SocketProcessor:具体处理请求,包括接受数据,处理数据,响应数据;
建立连接源码中serverSocketFactory.acceptSocket,就是调用socket的accept方法,建立连接,然后再processSocket方法中处理连接;
processSocket方法processSocket方法,主要是把建立的Socket包装成SocketProcessor,然后异步去处理Socket连接;
SocketProcessor线程处理主要是handler.process方法;这个方法代码太多,就不截取了,主要是看state = processor.process(wrapper);这一行,去处理响应的请求;到
AbstractHttp11Processor.process方法先去处理读取超时时间
setRequestLineReadTimeout如果会减去排队时间,然后再重新设置读取超时时间
请求解析后面就是依次解析请求行,请求头
解析请求行解析主要是看fill方法
fill方法fill方法后面再细说,再回到AbstractHttp11Processor.process
处理请求adapter就是处理完请求头和请求行之后,后续处理请求的方法
三.fill方法说明
fill方法该方法主要是处理AbstractInputBuffer.buf缓冲流属性,把操作系统的数据inputStream读到buf中去,InternalInputBuffer中几个重要属性:
pos:当前处理位置
lastValid:当前接受数据位置
end:请求头标记结束位置
该方法,把数据从pos位置放入,一直放入完成后,然后后移lastValid位置;每次调用fill方法时,都会把pos位置移到lastValid位置才会去再次调用;
1.读请求头
读请求头就是把inputStream读到上,再把lastValid后移相应的长度;
2.读请求体
读请求体,如果请求头读完剩下的长度不够4500则重新new 一个buf(缓冲区);然后把pos和lastValid位置移到end位置,意思是请求头后面的缓冲区,请求体会重复利用该位置,取一次就会重新再从end位置开始放数据;目的是为了节省空间;并且请求体tomcat不会解析,所以这部分数据交给应用来保存,不需要tomcat保存;(需要明白的一点是,每次进入这个fill方法时,说明在buf上的所有数据已经被读取完成,才会进入该方法,如果在第一次读请求体时,可能读最后一个请求行的时候,已经把请求体的前面一部分给读到buf上去了,这个时候如果再进入这个方法,说明读请求头时读到buf上的数据,已经被应用给取走了,被覆盖了也没关系);
四.ByteChunk属性
request对象主要是操作org.apache.coyote.Request,它的大部分属性,比如method,unparsedURIMB都是记录ByteChunk,主要是有三个属性
buff:指向AbstractInputBuffer.buf,操作具体的那个缓冲流
start:开始位置:
end:结束位置
该方法主要是为了节约性能,在解析时没有去真的解析,只是记录了当前属性在缓冲流中的位置,等真正用到时,再去解析成我们需要的格式;包括后续去读请求体也差不多;真正到了Servlet处理时,才去处理解析请求体
五.doRead方法
doRead方法doRead方法,主要在Servlet调用读请求体时去读取数据,remaining属性代表还剩余多少数据可读;当剩余还有剩余数据,则去读取,当读到的数据多余剩余数据时,就只从开始位置,读取剩余数据长度(缩小chunk块),然后把remaining减去真正读到的数据,这个时候会变成负数,这个负数会在最后end方法中去处理,如果end为负数的话,pos标记位会网前移动相应的位置,相当于会移动到这个请求体结束的位置,然后去处理下个请求;
六.end方法
end方法end方法在处理一次http请求完成后执行,该方法主要是为了把请求体读完,和doRead类似,这里就能看到,如果doRead的remaining属性为负数的话,则这个方法出栈后,就会pos就会被剪掉相应的长度;