赞
踩
在日常的开发中,很多开发的同学可能很少了解的HTTP协议的底层原理。HTTP协议的底层涉及到请求响应模型,HTTP的工作流程。cookie和session的原理等。本博文主要介绍计算机网络中HTTP的相关知识。帮助大家学习和理解HTTP协议的相关原理。
HTTP是HyperTextTransfer Protocol(超文本传输协议)的缩写。HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。HTTP协议通常承载于TCP协议之上,HTTPS就是也承载于TLS或SSL协议层之上。HTTP由请求和响应构成,是一个标准的客户端服务器模型(B/S)。HTTP协议永远都是客户端发起请求,服务器回送响应。HTTP协议是一个双向协议:我们在上网冲浪时,浏览器是请求方A,百度网站就是应答方B。双方约定用HTTP协议来通信,于是浏览器把请求数据发送给网站,网站再把一些数据返回给浏览器,最后由浏览器渲染在屏幕,就可以看到图片、视频了。
客户端发送请求时,⽤来指定服务器的域名 。 有了 Host 字段,就可以将请求发往同⼀台服务器上的不同网站。
服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据⻓度。 如下⾯则是告诉浏览器,本次服务器回应的数据⻓度是 1000 个字节,后⾯的字节就属于下⼀个回应了。
Connection 字段最常⽤于客户端要求服务器使⽤ TCP 持久连接,以便其他请求复⽤ 。 HTTP/1.1 版本的默认连接都是持久连接,但为了兼容⽼版本的 HTTP,需要指定 Connection ⾸部字段的值为Keep-Alive 。⼀个可以复⽤的 TCP 连接就建⽴了,直到客户端或服务器主动关闭连接。但是,这不是标准字段。
Content-Type 字段⽤于服务器回应时,告诉客户端,本次数据是什么格式。
Content-Encoding 字段说明数据的压缩⽅法。表示服务器返回的数据使⽤了什么压缩格式 。 下⾯表示服务器返回的数据采⽤了 gzip ⽅式压缩,告知客户端需要⽤此⽅式解压。 客户端在请求时,⽤ Accept-Encoding 字段说明⾃⼰可以接受哪些压缩⽅法。
方法 | 作用 |
OPTIONS(options) | 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。 |
HEAD(head) | 向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。 |
GET(get) | 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。 |
POST(post) | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form。 |
PUT(put) | 向指定资源位置上传其最新内容。 |
DELETE(delete) | 请求服务器删除Request-URL所标识的资源。 |
TRACE(trace) | 回显服务器收到的请求,主要用于测试或诊断。 |
CONNECT(connect) | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
属于提示信息,是协议处理中的⼀种中间状态,实际⽤到的⽐较少。
表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
表示客户端请求的资源发送了变动,需要客户端⽤新的 URL 新发送请求获取资源,也就是重定向。
表示客户端发送的报⽂有误,服务器⽆法处理,也就是错误码的含义。
表示客户端请求报⽂正确,但是服务器处理时内部发⽣了错误,属于服务器端的错误码。
三次握手流程:
为什么是三次握手?不是两次、四次?
三次握手才可以阻止重复历史连接的初始化(主要原因)
网路环境是错综复杂的,往往并不是如我们期望的⼀样,先发送的数据包,就先到达目标主机,可能会由于网路拥堵等乱七八糟的原因,会使得旧的数据包,先到达⽬标主机,那么这种情况下 TCP三次握手是如何避免的呢?
客户端连续发送多次 SYN 建⽴连接的报⽂,在网络拥堵情况下:
三次握手才可以同步双方的初始序列号
TCP 协议的通信双⽅, 都必须维护⼀个序列号, 序列号是可靠传输的⼀个关键因素,它的作⽤:
序列号在 TCP 连接中占据着常重要的作⽤,所以当客户端发送携带初始序列号的 SYN 报⽂的时 候,需要服务端回⼀个 ACK 应答报⽂,表示客户端的 SYN 报⽂已被服务端成功接收,那当服务端发送初始序 列号给客户端的时候,依然也要得到客户端的应答回应,这样⼀来⼀回,才能确保双⽅的初始序列号能被可靠的同步。
四次握⼿其实也能够可靠的同步双⽅的初始化序号,但由于第⼆步和第三步可以优化成⼀步,所以就成了三次握 ⼿。 ⽽两次握⼿只保证了⼀⽅的初始序列号能被对⽅成功接收,没办法保证双⽅的初始序列号都能被确认接收。
三次握手才可以避免资源浪费
如果只有两次握⼿,当客户端的 SYN 请求连接在⽹络中阻塞,客户端没有接收到 ACK 报⽂,就会重新发送SYN ,由于没有第三次握⼿,服务器不清楚客户端是否收到了⾃⼰发送的建⽴连接的 ACK确认信号,所以每收到⼀个 SYN就只能先主动建⽴⼀个连接。
这会造成什么情况呢? 如果客户端的SYN阻塞了,重复发送多次SYN报⽂,那么服务器在收到请求后就会建⽴多个冗余的无效的连接,造成不必要的资源浪费。
四次挥手的流程:
为什么需要四次挥手?
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL与TTL 的区别
为什么需要TIME_WAIT 状态?
主动发起关闭连接的⼀⽅,才会有 TIME-WAIT 状态。 需要 TIME-WAIT 状态,主要是两个原因:
TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢?
如上图黄色框框服务端在关闭连接之前发送的SEQ=301报⽂,被⽹络延迟了。 这时有相同端⼝的 TCP 连接被复⽤后,被延迟的 SEQ = 301 抵达了客户端,那么客户端是有可能正常接收 这个过期的报⽂,这就会产⽣数据错乱等严重的问题。 所以,TCP 就设计出了这么⼀个机制,经过 2MSL 这个时间,⾜以让两个⽅向上的数据包都被丢弃,使得原来 连接的数据包在⽹络中都⾃然消失,再出现的数据包⼀定都是新建⽴连接所产⽣的。
假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?
如果TIME-WAIT 等待⾜够⻓的情况就会遇到两种情况 ?
TIME_WAIT过多有什么危害?
如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器⽅主动发起的断开请求。 过多的 TIME-WAIT 状态主要的危害有两种:
如何优化TIME_WAIT?
打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项
如下的 Linux 内核参数开启后,则可以复⽤处于 TIME_WAIT 的 socket 为新的连接所⽤。 有⼀点需要注意的是,tcp_tw_reuse 功能只能⽤客户端(连接发起⽅),因为开启了该功能,在调⽤ connect() 函数时,内核会随机找⼀个 time_wait 状态超过 1 秒的连接给新的连接复⽤。
net.ipv4.tcp_max_tw_buckets
这个值默认为 18000,当系统中处于 TIME_WAIT 的连接⼀旦超过这个值时,系统就会将后⾯的 TIME_WAIT 连接 状态重置 。 这个⽅法过于暴⼒,⽽且治标不治本,带来的问题远⽐解决的问题多,不推荐使⽤
程序中使用SO_LINGER ,应⽤强制使⽤ RST 关闭
我们可以通过设置 socket 选项,来设置调⽤ close 关闭连接⾏为,我们可以通过设置 socket 选项,来设置调⽤ close 关闭连接⾏为 。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 有⼀个机制是保活机制。这个机制的原理是这样的: 定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作⽤,每隔⼀个时间间 隔,发送⼀个探测报⽂,该探测报文包含的数据⾮常少,如果连续几个探测报⽂都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应⽤程序 :
- 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:
- tcp_keepalive_time=7200:
- 表示保活时间是7200秒(2⼩时),也就2⼩时内如果没有任何连接相关的活动,则会启动保活机制
- tcp_keepalive_intvl=75:
- 表示每次检测间隔75秒;
- tcp_keepalive_probes=9:
- 表示检测9次⽆响应,认为对⽅是不可达的,从⽽中断本次的连接。
如果开启了 TCP 保活,需要考虑以下⼏种情况:
HTTP协议⾥有优缺点⼀体的双刃剑,分别是⽆状态、明⽂传输,同时还有⼀⼤缺点不安全。
HTTP 协议是基于 TCP/IP,并且使⽤了请求 - 应答的通信模式,所以性能的关键就在这两点⾥。
早期 HTTP/1.0 性能上的⼀个很⼤的问题,那就是每发起⼀个请求,都要新建⼀次 TCP 连接(三次握⼿),⽽且是 串⾏请求,做了⽆谓的 TCP 连接建⽴和断开,增加了通信开销。 为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信⽅式,也叫持久连接。这种⽅式的好处在于减少了 TCP 连接的᯿复建⽴和断开所造成的额外开销,减轻了服务器端的负载。 持久连接的特点是,只要任意⼀端没有明确提出断开连接,则保持 TCP 连接状态。
HTTP/1.1 采⽤了⻓连接的⽅式,这使得管道(pipeline)⽹络传输成为了可能。 即可在同⼀个 TCP 连接⾥⾯,客户端可以发起多个请求,只要第⼀个请求发出去了,不必等其回来,就可以发第 ⼆个请求出去,可以减少整体的响应时间。 举例来说,客户端需要请求两个资源。以前的做法是,在同⼀个TCP连接⾥⾯,先发送 A 请求,然后等待服务器做 出回应,收到后再发出 B 请求。管道机制则是允许浏览器同时发出 A 请求和 B 请求。 但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求。要是前⾯的回应特别慢,后⾯就会有许多请求 排队等着。这称为队头堵塞。
HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。 那 HTTP/2 相⽐ HTTP/1.1 性能上的改进
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是⼀样的或是相似的,那么,协议会帮你消除重 复的部分。 这就是所谓的HPACK算法:在客户端和服务器同时维护⼀张头信息表,所有字段都会存⼊这个表,⽣成⼀个索引号,以后就不发送同样字段了,只发送索引号,这样就提⾼速度了。
HTTP/2 不再像 HTTP/1.1 ⾥的纯⽂本形式的报⽂,⽽是全⾯采⽤了⼆进制格式,头信息和数据体都是⼆进制,并 且统称为帧(frame):头信息帧和数据帧。这样虽然对⼈不友好,但对计算机f非常友好,因为计算机只懂⼆进制,那么收到报⽂后,⽆需再将明⽂的报⽂转 成⼆进制,直接解析⼆进制报⽂,这增加了数据传输的效率 。
HTTP/2 的数据包不是按顺序发送的,同⼀个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。 每个请求或回应的所有数据包,称为⼀个数据流(Stream )。每个数据流都标记着⼀个独⼀⽆⼆的编号,其中规定客户端发出的数据流编号为奇数, 服务器发出的数据流编号为偶数客户端还可以指定数据流的优先级。优先级⾼的请求,服务器就先响应该请求。
HTTP/2 是可以在⼀个连接中并发多个请求或回应,⽽不⽤按照顺序⼀⼀对应。 移除了 HTTP/1.1 中的串⾏请求,不需要排队等待,也就不会再出现队头阻塞问题,降低了延迟,⼤幅度提⾼ 了连接的利⽤率。 举例来说,在⼀个 TCP 连接⾥,服务器收到了客户端 A 和 B 的两个请求,如果发现 A 处理过程⾮常耗时,于是就 回应 A 请求已经处理好的部分,接着回应 B 请求,完成后,再回应 A 请求剩下的部分。
HTTP/2 还在⼀定程度上改善了传统的请求--应答⼯作模式,服务不再是被动地响应,也可以主动向客户端发 送消息。 举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会⽤到的 JS、CSS ⽂件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)。
HTTP/2 协议其实还有很多内容,⽐如流控制、流状态、依赖关系等等 。HTTP/2 是如何提示性能的几个⽅向,它相比HTTP/1大大的提高了传输效率、吞吐能力。
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的, 这样内核才会将缓冲区⾥的数据返回给 HTTP 应⽤,那么当前 1 个字节数据没有到达时,后收到的字节数据只 能存放在内核缓冲区⾥,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。 有没有什么解决⽅案呢?既然是TCP 协议⾃身的问题,那干脆放弃 TCP 协议,HTTP/3 协议使用UDP 协议作为传输层协议!
HTTP/2 多个请求是跑在⼀个TCP 连接中的,那么当TCP丢包时,整个TCP 都要等待重传,那么就会阻塞该 TCP 连接中的所有请求。因为 TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且有序的,如果序列号较低的 TCP 段在⽹络传输 中丢失了,即使序列号较⾼的 TCP 段已经被接收了,应⽤层也⽆法从内核中读取到这部分数据,从HTTP 视⻆看,就是请求被阻塞了。
图中发送⽅发送了很多个packet,每个 packet 都有⾃⼰的序号,你可以认为是 TCP 的序列号,其中 packet 3 在 ⽹络中丢失了,即使 packet 4-6 被接收⽅收到后,由于内核中的 TCP 数据不是连续的,于是接收⽅的应⽤层就⽆ 法从内核中读取到,只有等到 packet 3 重传后,接收⽅的应⽤层才可以从内核中读取到数据,这就是 HTTP/2 的 队头阻塞问题,是在 TCP 层⾯发⽣的 。
发起HTTP 请求时,需要经过 TCP三次握⼿和TLS四次握⼿(TLS 1.2)的过程,因此共需要 3 个 RTT 的时延才 能发出请求数据 。另外, TCP 由于具有拥塞控制的特性,所以刚建⽴连接的 TCP 会有个慢启动的过程,它会对 TCP 连接 产⽣"减速"效果。
⼀个TCP 连接是由四元组(源 IP 地址,源端⼝,⽬标 IP 地址,⽬标端⼝)确定的,这意味着如果 IP 地址或者端⼝变动了,就会导致需要 TCP与TLS重新握⼿,这不利于移动设备切换⽹络的场景,比如4G网络环境切换成 WIFI。 这些问题都是 TCP 协议固有的问题,⽆论应⽤层的 HTTP/2 在怎么设计都⽆法逃脱。要解决这个问题,就必须把传输层协议替换成 UDP!
HTTP/2主要的问题在于多个 HTTP 请求在复⽤⼀个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求 的。所以⼀旦发⽣了丢包现象,就会触发 TCP 的᯿传机制,这样在⼀个 TCP 连接中的所有的 HTTP 请求都必须等 待这个丢了的包被重传回来。
这都是基于 TCP 传输层的问题,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP
UDP发⽣是不管顺序,也不管丢包的,所以不会出现 HTTP/1.1 的队头阻塞 和 HTTP/2 的⼀个丢包全部传问题。 ⼤家都知道UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议可以实现类似 TCP 的可靠性传输。 QUIC 有⾃⼰的⼀套机制可以保证传输的可靠性的。当某个流发⽣丢包时,只会阻塞这个流,其他流不会受到 影响。 TLS3 升级成了最新的 1.3 版本,头部压缩算法也升级成了 QPack 。 HTTPS 要建⽴⼀个连接,要花费 6 次交互,先是建⽴三次握⼿,然后是 TLS/1.3 的三次握⼿。QUIC 直接 把以往的 TCP 和 TLS/1.3 的 6 次交互合并成了 3 次,减少了交互次数。
我们深知,UDP是⼀个简单、不可靠的传输协议,⽽且是 UDP 包之间是⽆序的,也没有依赖关系。 UDP 是不需要连接的,也就不需要握⼿和挥⼿的过程,所以天然的就⽐TCP 快。 当然,HTTP/3 不仅仅只是简单将传输协议替换成了 UDP,还基于 UDP 协议在应⽤层实现了 QUIC 协议,它 具有类似 TCP 的连接管理、拥塞窗⼝、流量控制的⽹络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了, 所以不⽤担⼼数据包丢失的问题。 QUIC 协议的优点有很多,比如 :
QUIC协议也有类似 HTTP/2 Stream 与多路复⽤的概念,也是可以在同⼀条连接上并发传输多个Stream,Stream 可以认为就是⼀条 HTTP 请求。 由于 QUIC 使⽤的传输协议是 UDP,UDP 不关⼼数据包的顺序,如果数据包丢失,UDP 也不关⼼。 不过 QUIC 协议会保证数据包的可靠性,每个数据包都有⼀个序号唯⼀标识。当某个流中的⼀个数据包丢失了,即使该流的其他数据包到达了,数据也⽆法被 HTTP/3 读取,直到 QUIC重传丢失的报⽂,数据才会交给 HTTP/3。⽽其他流的数据报⽂只要被完整接收,HTTP/3 就可以读取到数据。这与 HTTP/2 不同,HTTP/2 只要某个流中的 数据包丢失了,其他流也会因此受影响。 所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独⽴的,某个流发⽣丢包了,只会影响该流,其他流不 受影响。
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在⼀起,需要分批次来握⼿,先 TCP 握⼿,再 TLS 握⼿。 HTTP/3 在传输数据前虽然需要 QUIC 协议握⼿,这个握⼿过程只需要1RTT,握⼿的⽬的是为确认双⽅的连接 ID,连接迁移是基于连接 ID 实现。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,⽽是QUIC 内部包含了 TLS,它在⾃⼰的帧会携带 TLS ⾥的“记 录”,再加上 QUIC 使⽤的是 TLS1.3,因此仅需 1 个 RTT 就可以同时完成建⽴连接与密钥协商,甚⾄在第⼆ 次连接的时候,应⽤数据包可以和 QUIC 握⼿信息(连接信息 + TLS 信息)⼀起发送,达到 0-RTT 的效果。
如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第⼀个数据包⼀起发送,可以做到 0-RTT:
在前⾯我们提到,基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端⼝、⽬的 IP、⽬的端⼝)确 定⼀条 TCP 连接,那么当移动设备的⽹络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连 接,然后重新建⽴连接,⽽建⽴连接的过程包含 TCP 三次握⼿和 TLS 四次握⼿的时延,以及 TCP 慢启动的减速 过程,给⽤户的感觉就是⽹络突然卡顿了⼀下,因此连接的迁移成本是很⾼的。 ⽽ QUIC 协议没有⽤四元组的⽅式来“绑定”连接,⽽是通过连接 ID来标记通信的两个端点,客户端和服务器可以各⾃选择⼀组 ID 来标记⾃⼰,因此即使移动设备的⽹络变化后,导致 IP 地址变化了,只要仍保有上下⽂信息(⽐如 连接 ID、TLS 密钥等),就可以“⽆缝”地复⽤原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
了解完 QUIC 协议的特点后,我们再来看看 HTTP/3 协议在 HTTP 这⼀层做了什么变化。 HTTP/3 同 HTTP/2 ⼀样采⽤⼆进制帧的结构,不同的地⽅在于HTTP/2 的⼆进制帧⾥需要定义Stream,⽽ HTTP/3 ⾃身不需要再定义 Stream,直接使⽤ QUIC ⾥的 Stream,于是 HTTP/3 的帧的结构也变简单了。
从上图可以看到,HTTP/3 帧头只有两个字段:类型和⻓度。 根据帧类型的不同,⼤体上分为数据帧和控制帧两⼤类,HEADERS 帧(HTTP 头部)和 DATA 帧(HTTP 包体) 属于数据帧
HTTP/3 在头部压缩算法这⼀⽅便也做了升级,升级成了 QPACK。与 HTTP/2 中的 HPACK 编码⽅式相似, HTTP/3 中的 QPACK 也采⽤了静态表、动态表及 Huffman 编码。 对于静态表的变化,HTTP/2 中的 HPACK 的静态表只有 61 项,⽽ HTTP/3 中的 QPACK 的静态表扩⼤到 91项
HTTP/2 和 HTTP/3 的 Huffman 编码并没有多⼤不同,但是动态表编解码⽅式不同。 所谓的动态表,在⾸次请求-响应后,双⽅会将未包含在静态表中的 Header 项更新各⾃的动态表,接着后续传输时 仅⽤ 1 个数字表示,然后对⽅可以根据这 1 个数字从动态表查到对应的数据,就不必每次都传输⻓⻓的数据,⼤⼤提升了编码效率 。
可以看到,动态表是具有时序性的,如果⾸次出现的请求发⽣了丢包,后续的收到请求,对⽅就⽆法解码出 HPACK 头部,因为对⽅还没建⽴好动态表,因此后续的请求解码会阻塞到⾸次请求中丢失的数据包重传过来。
HTTP/3 的 QPACK 解决了这⼀问题,那它是如何解决的呢?
QUIC 会有两个特殊的单向流,所谓的单项流只有⼀端可以发送消息,双向则指两端都可以发送消息,传输 HTTP 消息时⽤的是双向流,这两个单向流的⽤法: ⼀个叫 QPACK Encoder Stream, ⽤于将⼀个字典(key-value)传递给对⽅,⽐如⾯对不属于静态表的 HTTP 请求头部,客户端可以通过这个 Stream 发送字典;⼀个叫 QPACK Decoder Stream,⽤于响应对⽅,告诉它刚发的字典已经更新到⾃⼰的本地动态表了,后续 就可以使⽤这个字典来编码了。 这两个特殊的单向流是⽤来同步双⽅的动态表,编码⽅收到解码⽅更新确认的通知后,才使⽤动态表编码 HTTP 头部。
《小林图解网络》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。