HTTP协议又叫做超文本传输协议,是一种无状态,无连接,以请求-响应方式运行的协议。
也可以理解为:
(HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。)
HTTP协议属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议,在数据链路层使用MAC地址。
- TCP协议主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。TCP有可靠,面向连接的特点。
- IP协议主要解决网络路由和寻址问题。
- MAC地址主要解决相邻两台主机之间的寻址传输问题。
HTTP 传输的内容是「超文本」。
「超文本」,它是文字、图片、视频等的混合体,最关键有超链接,能从一个超文本跳转到另外一个超文本。
HTML 就是最常见的超文本了,它本身只是纯文字文件,但内部用很多标签定义了图片、视频等的链接,再经过浏览器的解释,呈现给我们的就是一个文字、有画面的网页了。
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送想要的数据过来,但是发送完,不会记录任何信息。
HTTP的无状态是指会话的无状态,什么是会话?会话是由一组请求和响应组成的,围绕一件相关事情的请求和响应,这些不同的请求和响应之间是需要进行数据传递的,但HTTP是无状态指的是本次请求和响应,与下一次的请求和响应是没有关系的,不会发生数据传递。 比如客户在购物平台购物场景下,会有这样的场景, 登录,下单,购物,客户想要购物,需要登录后才能进行处理,单独靠HTTP是无法完成的,发起登录的HTTP请求成功登录后,不跟踪客户的登录状态,不对后续购物的请求,进行用户登录数据信息传递,是无法满足业务逻辑的,需要借助一些比如cookie技术等。
HTTP 协议无状态特性的优点和缺点:
- 优点在于解放了服务器,每一次请求“点到为止”不会造成不必要连接占用
- 缺点在于每次请求会传输大量重复的内容信息
无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,就断开连接。采用这种方式可以节省传输时间。
早期这么做的原因是 HTTP 协议产生于互联网,因此服务器需要处理同时面向全世界数十万、上百万客户端的网页访问,但每个客户端(即浏览器)与服务器之间交换数据的间歇性较大(即传输具有突发性、瞬时性),并且网页浏览的联想性、发散性导致两次传送的数据关联性很低,大部分通道实际上会很空闲、无端占用资源。因此 HTTP 的设计者有意利用这种特点将协议设计为请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端。
随着时间的推移,网页变得越来越复杂,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次 TCP 连接就显得很低效。后来,Keep-Alive 被提出用来解决这效率低的问题。
协议类型://访问资源需要的凭证信息@服务器地址:端口号/资源层级 UNIX 文件路径?查询字符串#片段标识符
例如:
其中:
各个参数 | 描述 | 可否省略 |
协议类型 | 常见的协议类型有 http 和 https,访问 mysql 时的协议类型为 jdbc:mysql | 可以省略 省略后默认为 |
访问资源需要的凭证信息 | 一般就是登录信息(用户名、密码等),但是现在的网站进行认证一般不再通过 URL 进行,故一般省略 | 可以省略 |
服务器地址 | 服务器的地址可以是一个 IP 地址,也可以是一个域名(域名会通过 DNS 系统解析成一个具体的 IP 地址,可以使用 ping 域名 来得到该域名的 IP 地址),IP 地址用来描述网络上的一个具体位置,能够用来定位一个具体的主机 | 在 HTML 中可以省略 (比如 img、link、script、a 标签的 src 或者 href 属性),省略后表示服务器的 ip 或域名与当前 HTML 所属的 ip 或域名一致 |
端口号 | 端口号的主要作用是表示一台计算机中的特定进程所提供的服务,即用来区分一个主机上的不同程序。每个程序在访问网络的时候,都会关联上一个或多个端口号,通过端口号就能区分出当前的请求要给谁。 | 可以省略 当端口号省略时,浏览器会根据协议类型自动决定使用哪个端口号(如 http 协议默认使用80端口,https 协议默认使用443端口) |
资源层级 UNIX 文件路径 | 表示访问该服务器程序上某个资源的路径 | 可以省略 省略后相当于 |
查询字符串 | 查询字符串(query string)本质是一个键值对结构,且键值对之间使用 & 分割,键和值之间使用 = 分割。表示客户端给服务器传递的参数。该参数是 web 开发的一个重要参数,给前后端交互提供了很多可能性。该参数 key 和 value 的取值和个数,完全都是由程序员自己约定,因此可以通过这样的方式来自定制我们需要的信息给服务器 | 可以省略 |
片段标识符 | 片段标识符主要用于页面内跳转,例如跳转到当前页面的某个部分、章节等等 | 可以省略 |
URL 只能使用 ASCII 字符集来通过因特网进行发送。由于 URL 常常会包含 ASCII 集合之外的字符,URL 必须转换为有效的 ASCII 格式。
此时就需要对URL进行转义:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成
%XY
格式。
例子:
实际输入:https://www.hao123.com/
服务器接收到:https%3A%2F%2Fwww.hao123.com%2F
- 因为 HTTP 协议并没有规定报头部分的键值对有多少个,使用空行就相当于是报文的结束标记或报文和正文之间的分隔符(连续2个\r\n代表报头结束)
- HTTP 在传输层依赖 TCP 协议,TCP 是面向字节流的。如果没有这个空行,就会出现”粘包问题“
字段类型 | 名称 | 说明 | 实现版本 |
请求方法字段 (用于请求报文起始行) | GET | 从服务器获取指定资源 | HTTP 1.0 |
POST | 根据请求负荷(报文body)对指定的资源做出处理 | HTTP 1.0 | |
PUT | 向服务器传输文件 | HTTP 1.0 | |
HEAD | 获取报头header | HTTP 1.0 | |
DELETE | 请求服务器删除指定文件 | HTTP 1.0 | |
OPTIONS | 获取针对特定资源所支持的HTTP请求方法 | HTTP 1.1 | |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 | HTTP 1.1 | |
CONNECT | 要求用隧道协议连接代理服务器 | HTTP 1.1 | |
LINK | 请求与服务器建立链接关系 | HTTP 1.1 | |
UNLINE | 断开与服务器的链接 | HTTP 1.1 | |
报头字段 (用于请求/响应 header的字段) | Host | 客户端发送请求时,用来指定服务器的域名 | HTTP 1.0 |
Conent-Length | 服务器在返回数据时,会有 Content-Length 字段,表明本次响应的数据长度 | HTTP 1.0 | |
Connection | 常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用 | HTTP 1.0 | |
Conent-Type | 用于服务器回应时,告诉客户端,本次数据是什么格式 | HTTP 1.0 | |
Content-Encoding | 表示服务器返回的数据使用了什么压缩格式 | HTTP 1.0 |
根据RFC规范,GET的语义是请求从服务器上获取指定的资源,这个资源可以是文本,网页,图片,视频等。一般GET请求的参数都写在URL中。URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)
例如:你在我文章底部,敲入了留言后点击提交,浏览器就会执行一次 POST 请求,把你的留言文字放进了报文 body 里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器。
根据 RFC 规范,POST 的语义是根据请求正文对指定的资源做出处理(一般是客户端给服务器提交数据),具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body正文中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。
例如,你在我文章底部,敲入了留言后点击「提交」,浏览器就会执行一次 POST 请求,把你的留言文字放进了报文 body 里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器。
从RFC规范定义的语义来看:
注意:
上面是根据RFC规范定义的语义来分析的,实际应用中程序员可以不i根据RFC规范语义去实现GET和POST(比如让GET实现新增或删除数据的请求)
GET 和 POST 其实没有本质区别,使用 GET 的场景完全可以使用 POST 代替,使用 POST 的场景一样可以使用 GET 代替。但是在具体的使用上,还是存在一些细节的区别:
这里的安全是指信息是否会被泄漏。
网上有一种错误的解释: 如果实现登录页面,如果使用 GET 实现登录,GET 习惯上把数据放到URL中,此时就能看到浏览器的 URL 中显示当前的用户名和密码了,所以就并不安全;而 POST 习惯上会把数据放到body正文中,因此登录时就不能直接看到用户名和密码,就安全
正确的理解: 安全问题取决于是否加密以及加密算法的强度。这和将数据信息放到 URL 或 body正文 中无关,因为通过抓包,我们就可以得到这两部分的数据
PUT: 与 POST 相似,但是具有幂等特性,一般用于更新
DELETE: 删除服务器指定资源
OPTIONS: 返回服务器所支持的请求方法
HEAD: 与 GET 类似,只不过响应体不返回,只返回响应头
TRACE: 能显示服务器端收到的请求,测试的时候会用到
CONNECT: 将连接改为管道方式的代理服务器,用于SSL加密服务器的连接。
Host 的值表示客户端发送请求时,用来指定服务器主机的地址和端口(地址可以是域名,也可以是 IP;端口号可以省略或者手动指定)
Host: www.A.com
服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据长度。
Content-Length: 1000
Connection 字段最常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用。
HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定 Connection 首部字段的值为 Keep-alive。
Connection: Keep-alive
开启了 HTTP Keep-Alive 机制后, 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接。
Content-Type字段用于服务器回应时,告诉客户端,本次数据是什么格式
Content-Type: text/html; charset=utf-8
上面的类型表明,发送的是网页,而且编码是UTF-8。
客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。
Accept: */*
上面代码中,客户端声明自己可以接受任何格式的数据。
Content-Ecoding字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式。
Connect-Ecoding: gzip
上面表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。
客户端在请求时,用 Accept-Ecoding 字段说明自己可以接受哪些压缩方法。
Accept-Ecoding: gzip, deflate
1xx
类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
2xx
类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
「200 OK」是最常见的成功状态码,表示一切正常。如果是非 HEAD
请求,服务器返回的响应头都会有 body 数据。
「204 No Content」也是常见的成功状态码,与 200 OK 基本相同,但响应头没有body正文数据。
「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的body正文数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
3xx
类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段
Location
,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
4xx
类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
「400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误,具体什么错我们不知道。
「403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。
5xx
类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
「500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
「501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。
对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,这样的话 HTTP/1.1 的性能肯定肉眼可见的提升。所以,避免发送 HTTP 请求的方法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。
强制缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
强制缓存是利用下面这两个 HTTP 响应头部字段实现的,它们都用来表示资源在客户端缓存的有效期:
Cache-Control
, 是一个相对时间;Expires
,是一个绝对时间;如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:
当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304
,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。
上图就是一个协商缓存的过程,所以协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。
协商缓存的实现:
请求头部中的 If-None-Match
字段与响应头部中的 ETag
字段,这两个字段的意思是:
Etag
:唯一标识响应资源;If-None-Match
:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
当使用 ETag 字段实现的协商缓存的过程:
早期很多公司刚起步的时候,使用的应用层协议都是HTTP,而HTTP无论是用GET方法还是POST方法传参,都是没有经过任何加密的,因此早期很多的信息都是可以通过抓包工具抓到的,并不安全。
为了解决这个问题,于是出现了HTTPS协议,HTTPS实际就是在应用层和传输层协议之间加了一层加密层(使用SSL/TLS 安全协议),这层加密层本身也是属于应用层的,它会对用户的个人信息进行各种程度的加密。HTTPS在交付数据时先把数据交给加密层,由加密层对数据加密后再交给传输层。
当然,通信双方使用的应用层协议必须是一样的,因此对端的应用层也必须使用HTTPS,当对端的传输层收到数据后,会先将数据交给加密层,由加密层对数据进行解密后再将数据交给应用层。
HTTP对传输的信息没有加密,不安全;HTTPS针对传输的信息加密(TCP和HTTP之间加入了 SSL/TLS 安全协议),安全。
HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
HTTP 由于是明文传输,所以安全上存在以下三个风险:
解决方法:
为了实现信息的机密性,解决窃听的风险。
1.我们需要先了解对称加密和非对称加密的概念:
2.HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
3.对称加密的建立过程:
4.采用「混合加密」的方式的原因:
为了保证传输的内容不被篡改,我们需要对内容计算出一个「指纹」,然后同内容一起传输给对方。
对方收到后,先是对内容也计算出一个「指纹」,然后跟发送方发送的「指纹」做一个比较,如果「指纹」相同,说明内容没有被篡改,否则就可以判断出内容被篡改了。
那么,在计算机里会用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的「指纹」,这个哈希值是唯一的,且无法通过哈希值推导出内容。
通过哈希算法可以确保内容不会被篡改,但是并不能保证「内容 + 哈希值」不会被中间人替换,因为这里缺少对客户端收到的消息是否来源于服务端的证明。所以引出了以下的数字签名算法。
数字签名算法是基于非对称加密算法的。
公钥和私钥是可以双向加解密的:
所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密。
通过数字证书的方式保证服务器公钥的身份,解决冒充的风险。
前面我们知道:
但是这还远远不够,还缺少身份验证的环节,万一公钥是被伪造的呢?
在计算机里,有一个权威的机构就是 CA (数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的。
HTTP协议的最初版本。
由于HTTP协议采取的是 请求-响应 方式,且HTTP/1.0使用短链接,因此当这次的请求发送出去后,很长时间没有响应过来,就不能再次发送其他请求。这就是HTTP1.0的队头阻塞问题。
HTTP/1.0的队头阻塞是应用层 请求 的阻塞。
注意!!!
实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持,所以后面所有文章讨论 HTTP/1.1 都是建立在没有使用管道化的前提。大家知道有这个功能,但是没有被使用就行了。
Body正文
的部分;1. 无状态双刃剑:
2. 不安全:
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。
这就是所谓的 HPACK
算法:在客户端和服务器同时维护一张头信息表(静态/动态表),所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧和数据帧((使用「静态表、动态表、Huffman 编码」共同完成))。
这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率。
HTTP/2 为高频出现在头部的字符串和字段建立了一张静态表,它是写入到 HTTP/2 框架里的,不会变化的,静态表里共有 61
组,如下图:
静态表只包含了 61 种高频出现在头部的字符串,不在静态表范围内的头部字符串就要自行构建动态表,它的 Index 从 62
起步,会在编码解码的时候随时更新。
比如,第一次发送时头部中的「User-Agent
」字段数据有上百个字节,经过 Huffman 编码发送出去后,客户端和服务器双方都会更新自己的动态表,添加一个新的 Index 号 62。那么在下一次发送的时候,就不用重复发这个字段的数据了,只用发 1 个字节的 Index 号就好了,因为双方都可以根据自己的动态表获取到字段的数据。
所以,使得动态表生效有一个前提:必须同一个连接上,重复传输完全相同的 HTTP 头部。
我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。
而 HTTP/2通过 Stream 这个设计,多个 Stream 复用一条 TCP 连接,达到并发的效果,解决了 HTTP/1.1 队头阻塞的问题,提高了 HTTP 传输的吞吐量。
我们先来了解一下Stream,Message,Frame这三个概念:
例如:
并且:
总结:
当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接;
而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过 TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。
HTTP/1.1 不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资源。
比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:
如上图右边部分,在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。
在 Nginx 中,如果你希望客户端访问 /test.html 时,服务器直接推送 /test.css,那么可以这么配置:
- location /test.html {
- http2_push /test.css;
- }
那 HTTP/2 的推送是怎么实现的?
客户端发起的请求,必须使用的是奇数号 Stream;服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过 PUSH_PROMISE
帧传输 HTTP 头部,并通过帧中的 Promised Stream ID
字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。
如上图,在 Stream 1 中通知客户端 CSS 资源即将到来,然后在 Stream 2 中发送 CSS 资源,注意 Stream 1 和 2 是可以并发的。
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
因此HTTP/2.0的队头阻塞是传输层TCP的阻塞
对于常见的 HTTP 头部通过静态表和 Huffman 编码的方式,将体积压缩了近一半,而且针对后续的请求头部,还可以建立动态表,将体积压缩近 90%,大大提高了编码效率,同时节约了带宽资源。
不过,动态表并非可以无限增大, 因为动态表是会占用内存的,动态表越大,内存也越大,容易影响服务器总体的并发能力,因此服务器需要限制 HTTP/2 连接时长或者请求次数。
HTTP/2 实现了 Stream 并发,多个 Stream 只需复用 1 个 TCP 连接,节约了 TCP 和 TLS 握手时间,以及减少了 TCP 慢启动阶段对流量的影响。不同的 Stream ID 可以并发,即使乱序发送帧也没问题,但是同一个 Stream 里的帧必须严格有序。
另外,可以根据资源的渲染顺序来设置 Stream 的优先级,从而提高用户体验。
服务器支持主动推送资源,大大提升了消息的传输性能,服务器推送资源时,会先发送 PUSH_PROMISE 帧,告诉客户端接下来在哪个 Stream 发送资源,然后用偶数号 Stream 发送资源给客户端。
HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很完美了,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。
HTTP/2 通过头部压缩、二进制编码、多路Stream复用、服务器推送等新特性大幅度提升了 HTTP/1.1 的性能,而美中不足的是 HTTP/2 协议是基于 TCP 实现的,于是存在缺陷。
网络迁移需要重新连接:
一个 TCP 连接是由四元组(源 IP 地址,源端口,目标 IP 地址,目标端口)确定的,这意味着如果 IP 地址或者端口变动了,就会导致需要 TCP 与 TLS 重新握手,这不利于移动设备切换网络的场景,比如 4G 网络环境切换成 WiFi。
这些问题都是 TCP 协议固有的问题,因此HTTP/3.0把传输层协议替换成 UDP 协议!
但是:
我们知道,UDP 是一个简单、不可靠的传输协议,而且是 UDP 包之间是无序的,也没有依赖关系。而且,UDP 是不需要连接的,也就不需要握手和挥手的过程,所以天然的就比 TCP 快。
当然,HTTP/3 不仅仅只是简单将传输协议替换成了 UDP,还基于 UDP 协议在「应用层」实现了 QUIC 协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题
由于 QUIC 使用的传输协议是 UDP,UDP 不关心数据包的顺序,如果数据包丢失,UDP 也不关心。
不过 QUIC 协议会保证数据包的可靠性,每个数据包都有一个序号唯一标识。当某个流中的一个数据包丢失了,即使该流的其他数据包到达了,数据也无法被 HTTP/3 读取,直到 QUIC 重传丢失的报文,数据才会交给 HTTP/3。
而其他流的数据报文只要被完整接收,HTTP/3 就可以读取到数据。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
所以,QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。
在前面我们提到,基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从 4G 切换到 WiFi 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能
HTTP/2 虽然具有多个流并发传输的能力,但是传输层是 TCP 协议,于是存在以下缺陷:
HTTP/3 就将传输层从 TCP 替换成了 UDP,并在 UDP 协议上开发了 QUIC 协议,来保证数据的可靠传输。
QUIC 协议的特点:
当你在浏览器输入一个网址,如https://www.bilibili.com,按回车之后发生了什么?请从技术的角度描述,如浏览器、网络(UDP、TCP、HTTP等),以及服务器等各种参与对象上由此引发的一系列活动,请尽可能的涉及到所有的关键技术点。
DNS解析的过程就是寻找哪台机器上有你需要资源的过程,寻找的过程遵循就近原则。
输入一个网址并按回车的时候浏览器会根据输入的URL去查找对应的IP,具体过程如下:
(1)先查浏览器缓存,浏览器会保存一段时间内访问过的一些网址的DNS信息,不同浏览器保存的时常不等。
(2)如果没有找到对应的记录,这个时候浏览器会尝试调用操作系统缓存来继续查找这个网址的对应DNS信息。
(3)如果还是没找到对应的IP,那么接着会发送一个请求到路由器上,然后路由器在自己的路由器缓存上查找记录,路由器一般也存有DNS信息。
(4)如果还是没有,这个请求就会被发送到ISP(注:Internet Service Provider,互联网服务提供商,就是网络运营商,中国电信中国移动等),ISP也会有相应的ISP DNS服务器,就是本地DNS服务器,请求的域名基本上都能在这里找得到。
(5)如果还是没有的话, ISP的DNS服务器会将请求发向根域名服务器进行搜索。根域名服务器就是面向全球的顶级DNS服务器,共有13台逻辑上的服务器,从A到M命名,真正的实体服务器则有几百台,分布于全球各大洲。
(6)如果到了这里还是找不到域名的对应信息,那只能说明一个问题:这个域名本来就不存在,它没有在网上正式注册过。或者域名过期了。
这也就是为什么有时候打开一个新页面会有点慢,因为如果本地没什么缓存,查找域名的过程要这样递归地查询下去,查找完还要一层层的向上返回。
域名结构为:三级域名.二级域名.一级域名。例如"mp3.baidu.com",域名先是解析出这是个.com的域名,然后跑到管理.com域的服务器上进行进一步查询,然后是.baidu,最后是mp3,
DNS根据域名查询IP地址的过程为:浏览器缓存 --> 操作系统缓存 --> 路由器缓存-->本地(ISP)域名服务器缓存 --> 根域名服务器。