HTTP协议是“看不着、摸不着”的。因为在Java语言中,有太多的HTTP相关的库,如:HTTPClient、HTTPUrlConnection、Spring的RestTemplete等。因此在某种程度上从事Java相关的开发人员很难需要去深扒HTTP协议,而基本上都属于会用就好的状态。
编写本文的思路正如下面的文章目录。
目录
- 一、HTTP简介
- 二、什么是协议
- 2.1 协议的定义
- 三、Wireshark网络抓包工具
- 四、HTTP协议
- 4.1 前置知识
- 4.2 请求协议结构
- 4.2.1 描述
- 4.2.2 抓包查看
- 4.3 响应协议结构
- 4.3.1 描述
- 4.3.2 抓包查看
- 4.4 分包传输
- 4.4.1 结构图
- 4.4.2 优点
- 4.4.3 缺点
- 4.4.4 抓包查看
- 4.5 KeepAlive
- 4.5.1 Tomcat对KeepAlive的支持
- 4.5.2 TCP协议与HTTP协议中的KeepAlive区别
- 4.5.3 如何复用长连接
- 4.5.4 如何关闭长链接
- 4.5.5 疑问待解决
- 4.5.6 4.4 分包传输
- 五、HTTP协议如何解决TCP的粘包与拆包
一、HTTP简介
HTTP中文名称是超文本传输协议(英文:HyperText Transfer Protocol),HTTP是基于 TCP/IP 协议之上的应用层协议,HTTP是万维网数据通信的基础。
因此,在HTTP建立之前,需要先等客户端与服务端建立TCP连接。
二、什么是协议
在了解具体的HTTP协议之前,最好还是先了解什么叫协议,这对之后的学习其它协议肯定会有帮助。
2.1 协议的定义
协议,网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。
为了使数据在网络上从源到达目的,网路通信的参与方必须遵循相同的规则,这套规则称为协议(protocol),它最终体现在网络上传输的数据包的格式。
协议往往分为几个层次进行定义,分层定义是为了使某一层协议的改变不影响其他层次的协议。
备注:以上文字来源于百度百科
三、Wireshark网络抓包工具
Wireshark是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换。
在学习HTTP协议的过程中,因为HTTP协议的是看不见摸不着的,这给学习带来一定的难度。因此有必要用Wireshark抓包工具抓取一个HTTP协议下来观察。
Wireshark官网地址:https://wireshark.en.softonic.com/
先看一下Wireshark的界面:
显而易见:Wireshark抓包界面中分为顶部的菜单栏、过滤栏、上、中、下五个部分;
- 过滤栏:用于写一些过滤条件,避免被抓的包太多,导致眼花缭乱不方便观察包
- 上:请求列表,包含TCP、HTTP等等协议包
- 中:一共有5个Item。从上到下分别为:物理层、数据链路层、网络层、传输层、应用层。有时候还会多一个Item,多的Item只是Wireshark将应用层中携带的数据解析出来展示而已,网络包中实际没有。因此这一区域的数据,Wireshark主要是用来展示更加人性化、经过加工后的数据。
- 下:十六进制数据,是真正网络包中的数据;
四、HTTP协议
4.1 前置知识
在HTTP协议中,有很多的符号如:\n\r等,而也正是因为这些符号对HTTP协议解决拆包、粘包提供了基础
标识 | ASCII | 描述 | 字符 |
---|---|---|---|
CR | 13 | 回车 | \n |
LF | 10 | 换行 | \r |
SP | 32 | 空格 | |
COLON | 58 | 冒号 | : |
4.2 请求协议结构
4.2.1 描述
HTTP请求体中包含三个部分:① 请求行、② 请求头(Header)、③ 请求正文(Body)
概括为文字如下:
-
请求行:
包含三个部分:Method、URL、协议/版本,而这之前用空格隔开,在请求行的最后添加CRLF
-
请求头:
包含若干个键值对(格式为:key:value),在每个键值对之间都用CRLF隔开,在整个请求头的最后再加上一个CRLF;也就是说最后一个键值对的最后会有两个CRLF。
-
请求正文:
请求正文中主要是POST Method提交的数据,数据的格式可以有很多种,具体什么格式实在请求头中由Content-Type定义。在不考虑分包传输的情况下,请求正文中数据的字节数由Content-Length指定。
4.2.2 抓包查看
4.3 响应协议结构
4.3.1 描述
HTTP响应体中包含三个部分:① 状态行、② 响应头(Header)、③ 响应正文(Body)
概括为文字如下:
-
响应行:
包含三个部分:协议/版本、响应状态码、状态码描述符,而这之前用空格隔开,在请求行的最后添加CRLF
-
响应头:
不说了,响应头格式同Request中的请求头格式
-
响应正文:
不说了,响应正文同Request中的请求正文
4.3.2 抓包查看
看到这里,有的小伙伴可能会提出问题:
问:在上图中[响应头]与[响应正文]之间是什么数据呢?
答:这些数据是Wireshark为了辅助展示出的一些指标数据。实际上,在网络包中并没有这些数据,这里对比一下最下面的方块,看一下网络包中真正的数据。
问:在网络分层中,Hypertext Transfer Protocol已经是网络模型中的应用层了,那为什么上图的抓包中,最下面还有一层:Line-base text data:text/plain(1 lines)?
答:这个问题的答案其实和上一个问题的答案一致,这只是Wireshark为了展示的更加人性化,将数据抽离在了该层展示低而已,注意看最下面的红色框框,在Header结束之后就是两个CRLF,紧接着就是数据(hello world!)了。
4.4 分包传输
在HTTP协议中,除了将数据放在一个包中一次性发出,还可以通过分包方式将一个大批数据分在多个数据包中进行传输。需要注意的是,分包传输使用的也是同一个Socket连接。
在Header中通过Transfer-Encoding: chunked进行指定,该KV与Content-Length只能同时出现一个。
需要注意的是:在HTTP1.0版本协议中不支持分包。在HTTP1.1之后开始支持分包。
4.4.1 结构图
4.4.2 优点
- 当响应数据体量大时,避免浏览器出现忙等导致页面出现长时间的空白
- 当要发送的数据长度很难计算时,为了保证吞吐量,可以先将一部分数据通过一个子包先发出去
4.4.3 缺点
- 协议比较复杂
4.4.4 抓包查看
4.5 KeepAlive
HTTP协议如果不做特殊处理都是一种短连接,即为一次会话建立TCP/IP之后,客户端与服务端基于TCP/IP的连接之上通过HTTP协议完成会话后,连接也会被服务端断开。下一次的HTTP请求,还是需要为这个请求进行三次握手创建TCP连接,用完之后还需要四次挥手断开TCP连接,这大大减少了传输效率。
而KeepAlive的产生就是为了弥补上述的不足。
HTTP1.0版本中需要在Header中加入Connection: keep-alive来指定会话完毕之后不立即断开连接,而是有一定的复用时间。HTTP1.1版本中,默认开启该配置:Connection: keep-alive。
4.5.1 Tomcat对KeepAlive的支持
-
KeepAliveTimeout
意义在于当请求结束之后默认要等待该值的时长,以达到复用的效果。
在Tomcat9中,该参数值默认等于ConnectionTimeout的时长,而ConnectionTimeout在server.xml中默认是20000ms。ConnectionTimeout的含义在于:当连接建立起之后,可以忍受多久的时间客户端没有发数据过来。
看一段Tomcat9的源码:
<attribute name="keepAliveTimeout" required="false"> <p>The number of milliseconds this <strong>Connector</strong> will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the <strong>connectionTimeout</strong> attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.</p> </attribute>
但是需要注意,该值最好别设置的太久,当大流量打入之后可能会导致资源始终无法释放。
这里有一篇线上问题的复盘博客,可以参考一下:
-
MaxKeepAliveRequests
Tomcat某个时刻最大能维护多少的长链接,这个值在Tomcat9版本中默认为100。
看一段Tomcat9的源码:
<attribute name="maxKeepAliveRequests" required="false"> <p>The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100.</p> </attribute>
4.5.2 TCP协议与HTTP协议中的KeepAlive区别
需要注意的是:TCP协议中也有KeepAlive参数,但是与HTTP的KeepAlive还是有区别的;
-
TCP-KeepAlive
TCP的KeepAlive是为了连接保活,该机制有三个重要的参数:
① tcp_keepalive_intvl:保活探测消息的发送频率
② tcp_keepalive_probes:需要发送X次的探测消息但依然无效,则认为连接断开
③ tcp_keepalive_time:最后一次数据交换到TCP第一次发送探针消息的间隔时间
-
HTTP-Keep-Alive
HTTP发出响应之后不要断开连接,连接需要保持一段时间,以达到复用的效果。
4.5.3 如何复用长连接
首先需要了解的是,复用到底复用的是什么,看了上面的介绍,短连接中的每次请求都需要先进行TCP的连接导致TCP链路无法复用,因此这里的复用其实是针对TCP链路的复用。
可以配合连接池,如HttpClient就对长连接具有管理的功能,注意这里的管理肯定是针对长连接了,短连接压根就无法复用。
4.5.4 如何关闭长链接
当TCP连接需要关闭时,只需要在Header头中添加Connection: close即可。
4.5.5 疑问待解决
Tomcat9版本中默认KeepTimeout为20s,我也查看了一下SpringBoot对于Tomcat的配置,发现SpringBoot没有对Tomcat参数做调整,至少KeepTimeout用的就是Tomcat默认值。
那么在这样的情况下,KeepTimeout最终应该为20s才对,但是在抓包过程中却发现其值为60s.
抓包如下:
4.5.6 从JVM层面判断连接是否复用
TODO
五、HTTP协议如何解决TCP的粘包与拆包
TCP协议,数据传输都是stream式的,数据之间是没有流边界的,在这样情况下自然就会出现粘包与拆包的问题。那么基于TCP之上的HTTP协议当然也会面临这样的问题,那它是如何解决的呢?
了解了HTTP协议的结构之后,解决粘包拆包的问题就迎刃而解了。
HTTP的请求与响应,对于请求行、请求头、响应行、响应头而言,都可以通过CRLF与空格作为流的边界进行读取。
对于请求正文与响应正文而言,他们的字节长度在Header中的Content-Length中定义,假若,Header中没有Content-Length属性,取而代之的是Transfer-Encoding: chunked,那么就读取每一个chunk的size,继而再读取size个byte,知道读取到last-chunk的size为0。