HTTP知识速通
一.HTTP的基础概念
首先了解HTTP协议,他是目前主要使用在应用层的一种协议
http被称为超文本传输协议
而https则是安全的超文本传输协议
本章节的内容首先就是对http做一个简单的了解。
HTTP是一种应用层协议,是基于TCP/IP协议来传递信息的。
其中http1.0,http1.1,http2.0均为TCP实现,http3.0则是UDP实现。
说了半天,那啥是协议?
协议其实就是为了使数据在网络上从源头到达目的,网络通信的参与方必须遵循相同的规则,这套规则称为协议。
二.简单的HTTP协议
2.1URI和URL
在学习HTTP协议之前,首先介绍一下URI和URL
URI (统一资源标识符),如下图
URL(统一资源定位符)
其正是使用 Web 浏览器等 访问 Web 页面时需要输入的网页地址
那他们两个有什么区别呢?
- URI(Uniform Resource Identifier):
是一个广义的资源标识符,唯一标识某个资源(如网页、图片、文件等),不限定标识方式。
- URL(Uniform Resource Locator):
是URI的子集,不仅标识资源,还提供具体的访问方式(如协议、路径、服务器地址等)
也就是说url是uri的一种表示方式。
可能这样说还是有点迷糊,那我就举一个更为简单的例子吧
URI = 资源唯一标识
(如身份证号,唯一但无法直接定位)
URL = 资源唯一标识 + 定位方法
(如家庭住址,既能标识也能找到置)
换句话说URI只能标记它是一个资源,但是无法判断他在哪里,而URL不仅是资源还能定位
常见的uri还有邮箱(邮箱可以标识一个资源)
简言之:URL是URI的一种具体实现,用于定位资源;URI更通用,仅需标识资源。
2.2 HTTP工作过程
再有了上面的了解之后,俺们就要正式的迈入http的介绍了。
既然http是一种协议,或者说一种规矩,那他是如何工作的呢?
让我们接着往下看👀
当我们在浏览器输入一个网址,此时浏览器就会给对应的服务器发送一个 HTTP 请求,对应的服务器收到这个请求之后,经过计算处理,就会返回一个 HTTP 响应。
并且当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互过程。
这个过程的规定其实就是http协议,按照协议规定的格式进行信息传递。
基础术语:
- 客户端: 主动发起网络请求的一端
- 服务器: 被动接收网络请求的一端
- 请求: 客户端给服务器发送的数据
- 响应: 服务器给客户端返回的数据
2.3 http的特点
上述所说的请求和响应,其实也是http的特点,但他的特点远不如此。
注⚠️ :网络编程中,并不只有一发一收的形式,还有一对多,多对多的形式。
先来说一下http的优点吧:简单,灵活和易于拓展,应用广泛和跨平台
- 简单就是指他的报文格式
header + body
,头部信息也是key-value
简单文本的形式 - 灵活和易于拓展就是指他协议里面的请求方法,uri/url,状态码等没有被固定死,允许开发人员自定义和扩充
- 应用广泛和跨平台,就是指的台式的浏览器到手机上的各种APP都是
当然不仅仅有优点,也有缺点(虽然说是缺点,其实就是http的一种特色)
http是一种无状态,明文传输的协议,是不安全的
所谓的无状态,其实就是没有标识。
在第一次发送之后,如果再次由同一个客户端发送请求,虽然两者建立了链接,但仍旧会导致不知道谁发送的请求,因为没有对第一次访问的客户端做身份记录📝
举个例子:
例如在登录->添加购物车->下单->结算->支付,这系列操作都要知道用户的身份才行,但是服务器并不知道这些操作是关联的,就需要每一次都问一遍发信息的人的身份。
每一次的操作都要验证消息,这样的购物还舒服吗?别问,问就是酸爽!
明文传输就是字面意思,不加密,直接把原文发过去,可以直接肉眼查看,为调试工作带来了极大的方便
显然这样的做法肯定都是有问题的,那她不就是相当于信息裸奔吗,暴露在了光天化日之下。
万一里面有你的账号和密码,那.........
2.4 http的问题解决
在了解了http的无状态之后,我们会发现如果没状态岂不是每一次都要做一次身份认证,那岂不是太麻烦了,每次访问一个页面就要重新登陆一次。
为了解决这个问题于是乎就引入了cookie的概念,也就是身份记录。
Cookie技术通过在请求和响应报文中写入Cookie的信息来控制客户端的状态
Cookie会根据从服务器端发送的响应报文中的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie,当下一次客户端在往服务器发送请求的时候,客户端会自动在请求报文中加入cookie值,在发送出去
服务端发现客户端发送过来的Cookie后,会去检查到底是哪一个客户端发来的连接请求,然后对比服务器上的记录最后得到之前的状态。
为了解决明文传输问题和不安全,引入https。
https的内容会在后续介绍
3.HTTPS篇,也可以先去看一下https
三.HTTP的缓存技术
在之前的学习中我们会发现,如果每一次访问,都需要去服务端查找所需要的资源,如果我发送同样的求,那又需要建立连接,又要去服务端下面去查找内容,那这样岂不是很浪费嘛?
那有没有什么好的办法可以解决重复性请求的问题呢?
当然是有的,http协议通过引入缓存技术,从而解决这个问题
HTTP的缓存实现主要有两种方式,分别是强制缓存和协商缓存
3.1强制缓存
强制缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
如下图中,返回的是 200 状态码,但在 size 项中标识的是 from disk cache就是使用了强制缓存。
强制缓存是利用HTTP响应中的两个字段实现的,他们都标识资源在客户端缓存的有效期:
Cache-Control
, 是一个相对时间;Expires
,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Cache-control 选项更多一些,设置更加精细,所以建议使用Cache-Control 来实现强缓存。具体的实现流程如下:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
- 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
也就是在客户端将这些资源存储到了浏览器的缓存之中,当需要的再次访问的时候,会先查询本地缓存是否过期,如果过期则重新申请资源
3.2 协商缓存
但是强制缓存也会带来一个问题就是,如果在这段未过期时间内,如果请求资源的内容发生了改变,那在看旧版的岂不是不太好?
别担心,设计者也考虑到了这个问题(在这里之前可以先去看一下响应的状态码,4.2.1下面的内容)
于是乎就有了协商缓存这个东西。
当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是304
,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。
上面就是一个协商缓存的过程,所谓的协商其实就是判断要不要用本地缓存,那这个判断条件就是我们之前所说的----这个资源有没有被更新
协商缓存可以基于两种头部来实现,有两种不同的方式
- 第一种:请求头部中的
If-Modified-Since
字段与响应头部中的Last-Modified
字段实现
- 第二种:请求头部中的
If-None-Match
字段与响应头部中的ETag
字段
那他们的区别又是什么呢?
其实看完这些字段,就能知道,第一种方式是基于时间实现的,第二种则是基于一个唯一标识
前者会出现时间回溯或者被篡改等问题,所以一般都是采用后者(除此之外还可以解决其他比如Etag可以解决1s内多次刷新的操作等等)
这里需要注意一点:协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
下面是一个强制缓存和协商缓存的工作流程
简单说一下采用Etag字段实现的协商缓存的流程:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识这个唯一标识的值是根据当前请求的资源生成的;
- 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:
-
- 如果没有过期,则直接使用本地缓存;
- 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
- 服务器再次收到请求后,会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较:
-
- 如果值相等,则返回 304 Not Modified,不会返回资源;
- 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的ETag 唯一标识;
- 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。
四.HTTP报文
在经过上面的学习之后,我想对http已经有了一些认识吧,接下来,就让我们深入了解一下http报文,他到底是如何规范网络传输的。
下面是报文的样子。
- 请求报文
- 响应报文
这就是http协议的请求和响应的整体结构。下面就会围绕这些内容展开。
4.1 http请求(Request)
4.1.1 认识URL
在之前曾说过URL和URI,在浏览器上,我们大多数是通过URL直接访问数据。
在学习请求之前,要对URL有一个最为基础的了解。
其实访问一个网站,就是访问这个网站对应服务器内的文件资源。
通过这个构成也不难发现。
但是实际上URL有些字段是可以不写的,就比如登入信息,常见的形式就是协议名+服务器地址(也就是域名)
既然说到URL,有时候我们会发现为什么有的URL后面有一堆百分号以及一些乱七八糟的东西,这些东西又是干什么的?
接下来就来介绍一下
GET https://www.sogou.com/web?query=%E8%9B%8B%E7%B3%95&_asf=www.sogou.com&_ast=&w=01019900&p=40040100&ie=utf8&from=index-nologin&s_from=index&sut=1129&sst0=1646360982664&lkt=0%2C0%2C0&sugsuv=003B56A6DA4C2A82610BB3A8CFD5D583&sugtime=1646360982664 HTTP/1.1
这个URL就是一个搜索蛋糕的URL
我们会发现 query string 的有些值是 %E8%9B%8B%E7%B3%95
,0%2C0%2C0
通过 urlencode,知道 %E8%9B%8B%E7%B3%95
就是表示蛋糕
其实也就是所谓的url编码
需要 urlencode 的原因:
- 这是因为像 /、?、: 等这样的字符,已经被 url 当做特殊意义理解了,因此这些字符不能随意出现。如果某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义,即 urlencode
- 一个中文字符由 UTF-8 或者 GBK 这样的编码方式构成,虽然在 URL 中没有特殊含义,但是仍然需要进行转义,否则浏览器可能把 UTF-8/GBK 编码中的某个字节当做 URL 中的特殊符号
其次还有查询参数,也就是在URL 中的 `?query=`、`?key=value` 这类结构是 URL 查询参数(Query Parameters),它们的作用是向服务器传递额外的信息,通常用于动态网页或接口请求中。
也就是在服务器中的后端代码来提起url中这些查询参数的内容。
如果有多个查询参数,则通过&链接
4.1.2 认识方法(methed)
想必大家肯定都听说什么get,post方法,那他们到底是干什么的,有什么区别?
这里知道的同学可以先跳过😊
主要介绍一下GET请求和POST请求
根据 RFC 规范,GET 的语义是从服务器获取指定的资源
POST 的语义是根据请求负荷(报文body)对指定的资源做出处理
其实也没啥好说的,总结下来就是GET请求时获取定位资源,而POST请求则是对资源做出处理
其实主要要了解的一个问题就是-----GET和POST都是安全幂等的嘛?
首先说一下什么是安全和幂等:
- 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
- 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。
如果从 RFC 规范定义的语义来看:
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
做个简要的小结:
GET 的语义是请求获取指定的资源。GET 方法是安全、幂等、可被缓存的。
POST 的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 不安全,不幂等,(大部分实现)不可缓存。
注意, 上面是从 RFC 规范定义的语义来分析的。但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。
比如:
- 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等
- 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。
GET方法也是可以带上body,并且GET的查询参数也不是独有的,,其他比如POST请求也可以有参数
4.1.3 认识请求报头(header)
这个应该就能很好理解,其实所谓的请求报头就是附带的信息,用来描述这个请求。
接下来就简单介绍一下这些键值对的作用,下面的是比较常见的
通用首部字段
先介绍一下通用的首部字段:通用就是指在请求和响应中双方都会使用到的字段
- Cache-Control:之前在强制缓存那边涉及过,就是操作缓存的工作机制
- Connection:Connection 字段可用于管理持久连接(也称为长连接)。持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接,以便在同一个连接上发送多个请求和接收多个响应。在 HTTP/1.1 协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。在 HTTP/1.0 协议中,默认连接是非持久的。如果希望在 HTTP/1.0上实现持久连接,需要在请求头中显式设置 Connection: keep-alive。keep-alive:表示希望保持连接以复用 TCP 连接。close:表示请求/响应完成后,应该关闭 TCP 连接。
- Date:表示HTTP报文的日期和时间
- Pragma:是http1.1之前版本的历史遗留字段,仅作为与http/1.0的向后兼容而定义
- Transfer-Encoding:规定了传输报文主体时采用的编码方式。
- .......(还有几个,个人感觉到时候下去了解一下就行)
请求首部字段
上面简单介绍了一下常见的通用字段,接下来介绍一下request的请求报文的首部字段:
Accept 用户代理可处理的媒体类型
Accept-Charset 优先的字符集
Accept-Encoding 优先的内容编码
Accept-Language 优先的语言(自然语言)
Authorization Web认证信息
Expect 期待服务器的特定行为
From 用户的电子邮箱地址
Host 请求资源所在服务器
If-Match 比较实体标记(ETag)
If-Modified-Since 比较资源的更新时间
If-None-Match 比较实体标记(与 If-Match 相反)
If-Range 资源未更新时发送实体 Byte 的范围请求
If-Unmodified-Since 比较资源的更新时间(与If-Modified-Since相反)
Max-Forwards 最大传输逐跳数
Proxy-Authorization 代理服务器要求客户端的认证信息
Range 实体的字节范围请求
Referer 对请求中 URI 的原始获取方
TE 传输编码的优先级
User-Agent HTTP 客户端程序的信息
实体首部字段
Allow 资源可支持的HTTP方法
Content-Encoding 实体主体适用的编码方式
Content-Language 实体主体的自然语言
Content-Length 实体主体的大小(单位:字节)
Content-Location 替代对应资源的URI
Content-MD5 实体主体的报文摘要
Content-Range 实体主体的位置范围
Content-Type 实体主体的媒体类型
Expires 实体主体过期的日期时间
Last-Modified 资源的最后修改日期时间
这里涉及了长连接的问题,对他做出一个解释 : 在进行http传输之前首先要进行tcp的三次握手,建立之后才可以发送http报文,但是每一次发送完报文,它就会自动断开tcp链接,导致每一次发送请求都需要建立连接,造成了很大的开销,所以后续的版本里就引入了长连接,不会导致每次请求都需要建立和断开连接。
这就是http请求报文的一个形式,他里面还有很多的参数,了解就行了,如果需要设置,通过对应语言的请求和响应结构体设置即可。
4.2 http响应(response)
4.2.1 状态码(status code)
状态码表示访问一个页面的结果(如访问成功、失败,还是其它一些情况等等),它是一个3位的整数,从 1xx、2xx、3xx、4xx、5xx,分为五个大类,每个大类的含义都不同。以下介绍一些常见的状态码及它的状态码解释
- 1xx 状态码
属于信息性状态码,表示服务器已收到请求,需要客户端继续操作或等待进一步处理。它们通常用于临时响应,不会作为最终结果。
(这一块了解就行)
- 2xx 状态码
基本上200就是表示成功,这是最常见的。
- 3xx 状态码
301表示永久重定向
302表示临时重定向
(重定向相当于手机呼号的呼叫转移功能,如果我们换了一个手机号,就可以去办理该呼叫转移业务,使朋友拨打你的旧号码时,自动跳转到新号码)
304 表示可以调用本地缓存
- 4xx
404 notfound 即你发送的url请求在服务器上找不到
403 表示访问被拒绝
405 表示你访问的服务器不支持请求中的方法
- 5xx
500 表示服务器内部错误
504 表示当前服务器负载比较大,服务器处理单条请求的时耗很长,就会出现超时情况。
这里可能就会有同学有问题了,不是说http是无状态的吗,那为啥还要搞状态码呢?
这里明显就是对这个无状态的认识有点不太深入,这里的无状态是指浏览器不会保存客户端之前的请求。
而状态码则是描述单个请求的结果,换句话说也就是返回服务器当前的状态,所以并没有改变无状态的本质。
4.2.2 认识响应报头
响应报头其实和请求报头差不多,接下来简单介绍一下:
他的结构也是三部分,只不过就是将请求首部字段换成响应首部字段,其他方面都是一样的
响应首部字段
Accept-Ranges 是否接受字节范围请求
Age 推算资源创建经过时间
ETag 资源的匹配信息
Location 令客户端重定向至指定URI
Proxy-Authenticate 代理服务器对客户端的认证信息
Retry-After 对再次发起请求的时机要求
Server HTTP 服务器的安装信息
Vary 代理服务器缓存的管理信息
WWW-Authenticate 服务器对客户端的认证信息
五.HTTP的不同版本
HTTP从1.0-到HTTP1.1, HTTP2.0 , HTTP3.0这个演变,都有什么方面的改变?
目前常用的http协议是1.1,但是2.0和3.0的趋势也任然在上升。
5.1 HTTP1.0和1.1相比,提高了什么性能
HTTP1.0和HTTP1.1相比
- 使用长连接的方式改善了1.0版本短链接造成的开销
- 支持管道(pipeline)网络传输,只要第一个请求发送出去,就不必等其回来,就可以发送第二个请求,减少整体响应时间。
(什么是长连接,其实1.0版本中,每一次发送请求都需要建立连接,响应之后自动断开,所谓的长连接,就是不会自断断开,不用每一次发请求都要建立连接)
虽然1.1版本改进了不少,但是仍旧存在性能瓶颈问题:
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大,只能压缩 Body 的部分。
- 发送冗长的首部。每次互相发送相同的首部造成的浪费较多
- 虽然解决了请求的拥挤,但是如果响应但服务端的响应也是依次的,会导致队头阻塞的问题(说白了就是第一个响应没发出去,后面的响应都发不出去)
- 没有请求优先控制
- 请求只能从客户端开始,服务器只能被动响应。
5.2 HTTP/2 做了什么优化?
5.2.1 http2的优改进
首先要知道HTTP/2是基于HTTPS的,所以安全性是有保障的。
接下来看一张图( ﹡ˆoˆ﹡ )
在1.1上的改进
- 头部压缩
- 二进制格式
- 并发传输
- 服务器主动推送资源
1.头部压缩
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。
这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
2.二进制格式
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)
这样做就不需要再将明文转化为二进制了,还增加了传输效率。
这个主要和编码有关,就不过多介绍了,感兴趣可以自行了解₍˄·͈༝·͈˄*₎◞ ̑̑
3.并发传输
我们都知道HTTP/1.1是基于请求响应模型,也就是完成了这样一个请求响应的事务之后,才会处理下一个事务。
这样就会导致你在发送完一个请求,就会进入等待,但是后面还有请求,由于你队头在等待,就会导致后面的请求也进入等待,也就是造成了队头阻塞的问题。
而HTTP2就很牛逼了,引出来stream的概念,多个stream复用一个tcp连接
其实也就是并发的发送数据,这些数据都是一个个的帧(frame),
这里就会想他们都是帧,咋区分哪几个帧是一个数据拆分的啊?
针对这个问题,采用了独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。如下图所示
4.服务器推送
也就是改变了传统的请求响应模式,服务端也可以主动发请求。
客户端和服务器双方都可以建立 Stream,但Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号而服务器建立的 Stream 必须是偶数号。
知道这些内容就行了,举一个简单的例子:
下面这张图的请求部分可以看见stream4,这个其实就是服务端主动向客户端推送的。
5.2.2 http2点缺陷
虽然http2看似解决了队头阻塞的问题,其实并没有,只不过问题不出在http,而是在tcp上。在文章的开头说过http2是基于tcp的。
那为什么这样做会导致tcp队头阻塞呢?
TCP是基于字节流的协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用。那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
这样解释可能比较含糊,我说的简单一点:再此之前,我们要知道一点那就是tcp是可靠的,准确的。正是因为如此,如果说中间传输的过程中,如果一个帧丢失了,也就是所谓的丢包了,就会导致tcp重传,只有这个包被重新传入,其它的http请求才会正常工作。
5.3 HTTP/3又有什么优化呢?
首先就是HTTP/1.1和HTTP/2都存在队头阻塞的问题。
1.1是通过管道解决了请求队头阻塞,但是没有解决响应的队头阻塞
2通过多个请求复用一个tcp连接,解决了http的队头阻塞,但是一旦丢包就会导致tcp层队头阻塞。
HTTP/2队头阻塞的问题是由tcp导致的,所以HTTP/3就把他换成了UDP
UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
QUIC主要有三个特点:
- 无队头阻塞
- 更快的连接建立
- 连接迁移
1.无队头阻塞
QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。
他有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
也就是QUIC 连接上的多个 Stream 之间并没有依赖,都是独立的,某个流发生丢包了,只会影响该流,其他流不受影响。
2.更快的连接建立
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层,openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在传输数据前虽然需要 QUIC 协议握手,但是这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商。
(RTT : 数据从发送端到接收端再返回发送端所需的时间)
甚至,在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
3.连接迁移
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从4G切换到WIFI时,意味着IP地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含TCP三次握手和TLS 四次握手的时延,以及TCP慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而QUIC协议没有用四元组的方式来“绑定”连接,而是通过连接ID来标记通信的两个端点,客户端和服务器可以各自选择一组ID来标记自己,因此即使移动设备的网络变化后,导致IP地址变化了,只要仍保有上下文信息(比如连接ID.TLS密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
所以,QuUC是一个在UDP之上的伪TCP+TLS+HTTP/2的多路复用的协议。
QUlC是新协议,对于很多网络设备,根本不知道什么是QUIC,只会当做UDP,这样会出现新的问题,因为有的网络设备是会丢掉UDP包的,而QUIC是基于uDP实现的,那么如果网络设备无法识别这个是QUIC包,那么就会当作UDP包,然后被丢弃。
HTTP/3现在普及的进度非常的缓慢,不知道未来UDP是否能够逆袭TCP.