Tomcat是如何处理请求的(下)

文章介绍了Tomcat如何通过不同的IO模型(BIO,NIO)获取和解析HTTP请求的字节流。在BIO模式中,Acceptor线程接收Socket连接并交给线程池处理;NIO模式下,Acceptor线程接收连接后注册给Poller线程,由Poller处理读写事件。解析字节流遵循HTTP协议格式,通过请求行、请求头和请求体的标识来划分数据。文章还讨论了长连接下请求结束的判断方式以及Tomcat处理请求的整体流程。
摘要由CSDN通过智能技术生成

上篇文章我们说了Tomcat解析字节流得到HttpServletRequest对象后是如何在Tomcat内部流程并交给Servlet执行的,那么本篇文章,我们来详细解释一下Tomcat是如何根据HTTP协议来解析字节流的。

获取字节流

我们先从获取字节流开始,Tomcat底层是通过TCP协议,也就是Socket来获取网络数据的,那么从Socket上获取数据,就涉及到IO模型,在Tomcat8以后,就同时支持了NIO和BIO。

在Tomcat中,有一个组件叫做Connector,他就是专门用来接收Socket连接的,在Connector内部有一个组件叫ProtocolHandler,它有好几种实现:

1、Http11Protocol

2、Http11NioProtocol

3、Http11AprProtocol

这样,我们应该就能更加明白了,就是通过这三个类来支持各种Io模型的,我们先来看Http11Protocol。

Http11Protocol(BIO)

在Http11Protocol中存在一个组件,叫loEndpoint,而在JloEndpoint中存在一个组件,叫Acceptor,Acceptor是一个线程,
这个线程会不停的从ServerSocket中获取Socket连接,每接收到一个Socket连接之后,就会将这个Socket连接扔到一个线程池中
由线程池中的线程去处理Socket,包括从Socket中获取数据,将响应结果写到Socket中。

所以,如果大家现在用的是Tomcat中的BIo模式,如果要进行性能调优,就可以调整该线程池的大小,默认10个核心线程,最大为200个线程。

在这里插入图片描述


Http11NioProtocol(NIO)

再来看Htp11NioProtocol,和Http11Protocol非常类似,在它的底层有一个NioEndpoint,NioEndpoint中也存在一个Acceptor线程,但是需要注意的是,现在虽然是No模型,但是Acceptor线程在接收Socket连接时,并不是非阻塞的方式,仍然是通过阻塞的方式来接收Socket连接。

Acceptor线程每接收到一个Socket连接后,就会将该Socket连接注册给一个Poller线程,后续就由这个Poller线程来负责处理该Socket上读写事件,默认情况下Tomcat中至少存在两个Poller线程,要么就是CPU核心数个Poller线程,值得注意的是,Poller线程不是通过线程池来实现的,是通过一个Poller数组来实现的,因为在NIO模型下,一个Poller线程要负责处理多个Socket连接中的读写事件,所以Acceptor线程在接收到一个Socket连接后,要能够比较方便的拿到Poller线程,如果用线程池,那么就不方便拿线程对象了,而用Poller数组,就会轮询拿到Poller线程,并把接收到的Socket连接注册给Poller线程。

另外,某个Poller线程接收到一个读写事件后,会将该事件派发给另外一个线程池进行处理。所以,NIO模型是更加复杂的,总结一下就是:

1、Acceptor线程负责接收Socket连接

2、Poller线程负责接收读写事件

3、线程池负责处理读写事件

所以,如果大家现在用的是Tomcat中的NIO模式,如果要进行性能调优,可以调整该Poller数组的大小,也可以调整线程池的大小。

在这里插入图片描述

解析字节流

不同的IO模型只是表示从Socket上获取字节流的方式不同而已,而获取到字节流之后,就需要进行解析了,之前我们说过,Tomcat需要按照HTTP协议的格式来解析字节流,下面是HTTP协议的格式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-miLBYkh0-1684574647196)(images/http.jpg)]

所以,浏览器或者HttpClient在发送数据时,同样需要按照Http协议来构造数据(字符串),然后将字符串转成字节发送出去,所以Tomcat解析字节流的逻辑就是:

1、从获得的第一个字节开始,遍历每个字节,当遇到空格时,那么之前所遍历到的字节数据就是请求方法

2、然后继续遍历每个字节,当遇到空格时,那么之前遍历到的字节数据就是URL

3、然后继续遍历每个字节,当遇到回车、换行符时,那么之前遍历到的字节数据就是协议版本,并且表示请求行遍历结束

4、然后继续遍历当遇到一个回车符和换行符时,那么所遍历的数据就是一个请求头

5、继续遍历当遍历到两个回车符和换行符时,那么所遍历的数据就是一个请求头,并且表示请求头全部遍历完毕

6、剩下的字节流数据就表示请求体

值得注意的是,如果使用的是长连接,那么就有可能多个HTTP请求共用一个Socket连接,那么Tomcat在获取并解析Socket连接中的字节流时,该如何判断某个HTTP请求的数据在哪个位置结束了呢?也就是如何判断一个请求的请求体何时结束?

有两种方式:

1、设置Content-Length:在发送请求时直接设置请求体的长度,那么Tomcat在解析时,自然就知道了当前请求的请求体在哪个字节结束

2、设置Transfer-Encoding为chunk: 也就是分块传输,在发送请求时,按如下格式来传输请求体,[chunk size][\rln][chunk data]
[\r\n][chunk size][\r(n][chunk data][\rln][chunk size = 0][\rln][\rln],注意最后的chunk size=0和两个回车换行符,只要Tomcat解析到这些时,就表示接收到了最后一块,也就表示请求体结束了

总结

经过两篇文章的分析,我们可以总结一下Tomcat处理请求的流程:

1、浏览器在请求一个Servlet时,会按照HTTP协议构造一个HTTP请求,通过Socket连接发送给Tomcat

2、Tomcat通过不同的IO模型都可以接收到Socket的字节流数据

3、接收到数据后,按HTTP协议解析字节流,得到HttpServletRequest对象

4、再通过HttpServletRequest对象,也就是请求信息,找到该请求对应的Host、Context、Wrapper

5、然后将请求交给Engine层处理

6、Engine层处理完,就会将请求交给Host层处理

7、Host层处理完,就会将请求交给Context层处理

8、Context层处理完,就会将请求交给Wrapper层处理

9、Wrapper层在拿到一个请求后,就会生成一个请求所要访问的Servlet实例对象

10、调用Servlet实例对象的service()方法,并把HttpServletRequest对象当做入参

11、从而就调用到servlet所定义的逻辑

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值