finish UDP 的构成

master
Omooo 5 years ago
parent b3575fc437
commit 305681050e
  1. 0
      blogs/computer_network/Web 性能权威指南/HTTP 1.1.md
  2. 318
      blogs/computer_network/Web 性能权威指南/HTTP 2.0.md
  3. 0
      blogs/computer_network/Web 性能权威指南/TCP 的构成.md
  4. 173
      blogs/computer_network/Web 性能权威指南/UDP 的构成.md
  5. 5
      blogs/computer_network/Web 性能权威指南/Web 性能优化最佳实践.md
  6. 4
      blogs/computer_network/Web 性能权威指南/传输层安全(TLS).md
  7. 122
      blogs/computer_network/Web 性能权威指南/针对浏览器的优化建议.md
  8. BIN
      books/计算机网络/Web性能权威指南.pdf
  9. BIN
      images/network/Web 性能权威指南/ICE.png
  10. BIN
      images/network/Web 性能权威指南/IP 地址网络转换器.png
  11. BIN
      images/network/Web 性能权威指南/IPv4 首部.png
  12. BIN
      images/network/Web 性能权威指南/NAT 穿透.png
  13. BIN
      images/network/Web 性能权威指南/STUN.png
  14. BIN
      images/network/Web 性能权威指南/TURN.png
  15. BIN
      images/network/Web 性能权威指南/UDP 首部.png

@ -1,159 +1,159 @@
---
HTTP/2.0
---
#### 目录
1. 思维导图
2. 前言
3. 设计和技术目标
* 二进制分帧层
* 流、消息与帧
* 多项请求与响应
* 请求优先级
* 服务器推送
* 首部压缩
4. 二进制分帧层简介
#### 前言
HTTP/2.0 的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩 HTTP 首部字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。为达到这些目标,HTTP/2.0 还会给我们带来大量其他协议层面的辅助实现,比如新的流量控制、错误处理和更新机制。
HTTP/2.0 不会改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念一如往常。但是,HTTP/2.0 修改了格式化数据(分帧)的方式,以及客户端与服务器间传输这些数据的方式。这两点统帅全局,通过新的组帧机制向我们的应用隐藏了所有复杂性。换句话说,所有原来的应用都可以不必修改而在新协议运行。
#### 设计和技术目标
HTTP/1.x 的设计初衷主要是实现要简单:HTTP/0.9 只用一行协议就启动了万维网;HTTP/1.0 则是对流行的 0.9 扩展的一个正式说明;HTTP/1.1 则是 IETF 的一份官方标准。因此,HTTP 0.9~1.x 只描述了现实是怎么一回事:HTTP 是应用最广泛、采用最多的一个互联网应用协议。
然而,实现简单是以牺牲应用性能为代价的,而这正是 HTTP/2.0 要致力于解决的:HTTP/2.0 通过支持首部字段压缩和在同一个连接上发送多个并发消息,让应用更有效地利用网络资源,减少感知的延迟时间。而且,它还支持服务器到客户端的主动推送机制。
##### 二进制分帧层
HTTP/2.0 性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
![](https://i.loli.net/2019/06/05/5cf730c6bc34987526.png)
这里所谓的层,指的是位于套接字接口与应用可见的高层 HTTP API 之间的一个新机制:HTTP 的语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。HTTP/1.x 以换行符作为纯文本的分隔符,而 HTTP/2.0 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。
##### 流、消息和帧
新的二进制分帧机制改变了客户端与服务器之间交互数据的格式,为了说明这个过程,我们需要了解 HTTP/2.0 两个新的概念:
* 流
已建立的连接上的双向字节流。
* 消息
与逻辑消息对应的完整的一系列数据帧。
* 帧
HTTP/2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。
![](https://i.loli.net/2019/06/05/5cf7343f8610140755.png)
所有 HTTP/2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一个或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
这简简单单的几句话里浓缩了大量的信息,要理解 HTTP/2.0,就必须理解流、消息和帧这几个基础概念:
1. 所有通信都在一个 TCP 连接上完成
2. 流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2 ~N)
3. 消息是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成
4. 帧是最小的通信单位,承载着特定类型的数据,如 HTTP 首部、负荷,等等
简言之,HTTP/2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应的,很多流可以并行的在同一个 TCP 连接上交换消息。
##### 多向请求与响应
在 HTTP/1.x 中,如果客户端想发送多个并行的请求以及改进性能,那么必须使用多个 TCP 连接。这是 HTTP/1.x 交互模型的直接结果,该模型会保证每个连接每次只交互一个响应(多个响应必须排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。
HTTP/2.0 中新的二进制分帧层突破了这些限制,实现了多向请求和响应:客户端和服务器可以把 HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。
![](https://i.loli.net/2019/06/05/5cf742f19abea24700.png)
上图中包含了同一个连接上多个传输中的数据流:客户端正在向服务器传输一个 DATA 帧(stream 5),与此同时,服务器正向客户端乱序发送 stream 1 和 stream 3 的一系列帧。此时,一个连接上有 3 个请求/响应并行交换!
把 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP/2.0 最重要的一项增强。事实上,这个机制会在整个 Web 技术栈中引发一系列连锁反应,从而带来巨大的性能提升,因为:
* 可以并行交错的发送请求,请求之间互不影响
* 可以并行交错的发送响应,响应之间互不干扰
* 只使用一个连接即可并行发送多个请求和响应
* 消除不必要的延迟,从而减少页面加载的时间
* 不必再为绕过 HTTP/1.x 限制而多做很多工作
总之,HTTP/2.0 的二进制分帧机制解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,就是应用速度更快,开发更简单,部署成本更低。
> 支持多向请求与响应,可以省掉针对 HTTP/1.x 限制所费的那些脑筋和工作,比如拼接文件、图片精灵、域名分区。类似的,通过减少 TCP 连接的数量,HTTP/2.0 也会减少客户端和服务器的 CPU 及内存占用。
##### 请求优先级
把 HTTP 消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,进一步提升性能。为了做到这一点,每个流都可以带一个 31 比特的优先值:
* 0 表示最高优先级
* 2^31 - 1 表示最低优先级
有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。具体来讲,服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。
##### 服务器推送
HTTP/2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确的请求。
![](https://i.loli.net/2019/06/05/5cf7598f14b9933893.png)
建立 HTTP/2.0 连接后,客户端与服务器交换 SETTINGS 帧,借此可以限定双向并发的流的最大数量。因此,客户端可以限定推送流的数量,或者通过把这个值设置为 0 而完全禁用服务器推送。
为什么需要这样一个机制呢?通常的 Web 应用都由几十个资源组成,客户端需要分析服务器提供的文档才能逐个找到它们。那为什么不让服务器提前就把这些资源推送给客户端,从而减少额外的时间延迟呢?服务器已经知道客户端下一步要请求什么资源了,这时候服务器推送即可派上用场。上篇文章所说的 “嵌入资源” 也可以算作服务器推送。把资源直接插入到文档中,就是把资源直接推送给客户端,而无需客户端请求。在 HTTP/2.0 中,唯一的不同就是把这个过程从应用中拿出来,放到 HTTP 协议本身来实现,而且还带来了如下好处:
* 客户端可以缓存推送过来的资源
* 客户端可以拒绝推送过来的资源
* 推送资源可以由不同的页面共享
* 服务器可以按照优先级推送资源
最后还有一点,就是推送的资源将直接进入客户端缓存,就像客户端请求了似的。不存在客户端 API 或 JavaScript 回调方法等通知机制,可以用于确定资源何时到达。整个过程对运行在浏览器中的 Web 应用来说好像根本不存在。
##### 首部压缩
HTTP 的每一次通信都会携带一组首部,用于描述传输的资源及其属性。在 HTTP/1.x 中,这些元数据都是以纯文本形式发送的,通常会给每个请求增加 500~800 字节的负荷。如果算上 HTTP Cookie,增加的负荷通常会达到上千字节。为减少这些开销并提升性能,HTTP/2.0 会压缩首部元数据:
* HTTP/2.0 在客户端和服务器端使用 “首部表” 来跟踪和存储之前发送的键值对,对于相同的数据,不在通过每次请求和响应发送。
* 首部表在 HTTP/2.0 的连接存续期内始终存在,由客户端和服务器共同渐进的更新。
* 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值
于是,HTTP/2.0 连接的两端都知道已经发送了哪些首部,这些首部的值是什么,从而可以针对之前的数据只编码发送差异数据。
![](https://i.loli.net/2019/06/05/5cf78307e362058386.png)
请求与响应首部的定义在 HTTP/2.0 中基本没有改变,只是所有首部键必须全部小写,而且请求行要独立为 :method、:scheme、:host 和 :path 等这些键值对。
在前面的例子中,第二次请求只需要发送变化了的路径首部(:path),其他首部没有变化,不用再发送了。这样就可以避免传输冗余的首部,从而显著减少每个请求的开销。
#### 二进制分帧层简介
HTTP/2.0 的根本改进还是新增的长度前置的二进制分帧层。与 HTTP/1.x 使用换行符分隔纯文本不同,二进制分帧层更加简洁,通过代码处理起来更简单也更有效。
建立了 HTTP/2.0 连接后,客户端与服务器会通过交换帧来通信,帧是基于这个新协议通信的最小单位。所有帧都共享一个 8 字节的首部,如下图所示,其中包含帧的长度、类型、标志,还有一个保留位和一个 31 位的流标识符。
![](https://i.loli.net/2019/06/05/5cf79db949a0866805.png)
* 16 位的长度前缀意味着一帧大约可以携带 64 KB 数据,不包括 8 字节首部
* 8 位的类型字段决定如何解释帧其余部分的内容
* 8 位的标志字段允许不同的帧类型定义特定于帧的消息标志
* 1 位的保留字段始终置为 0
* 31 位的流标识符唯一标识 HTTP/2.0 的流
知道了帧类型,解析器就知道该如何解析帧的其余内容了,HTTP/2.0 规定了如下帧类型:
* DATA:用于传输 HTTP 消息体
* HEADERS:用于传输关于流的额外的首部字段
* PRIORITY:用于指定或重新指定引用资源的优先级
* RST_STREAM:用于通知流的非正常终止
* SETTINGS:用于通知两端通信方式的配置数据
* PUSH_PROMISE:用于发出创建流和服务器引用资源的要约
* PING:用于计算往返时间,执行 “活性” 检查
* GOAWAY:用于通知对端停止在当前连接中创建流
* WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制
* CONTINUATION:用于继续一系列首部块片段
服务器可以利用 GOAWAY 类型的帧告诉客户端要处理的最后一个流的 ID,从而消除一些请求竞争,而且浏览器也可以据此智能的重试或取消 “悬着的” 请求。这也是保证复用连接安全的一个重要和必要的功能。
---
HTTP/2.0
---
#### 目录
1. 思维导图
2. 前言
3. 设计和技术目标
* 二进制分帧层
* 流、消息与帧
* 多项请求与响应
* 请求优先级
* 服务器推送
* 首部压缩
4. 二进制分帧层简介
#### 前言
HTTP/2.0 的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩 HTTP 首部字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。为达到这些目标,HTTP/2.0 还会给我们带来大量其他协议层面的辅助实现,比如新的流量控制、错误处理和更新机制。
HTTP/2.0 不会改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念一如往常。但是,HTTP/2.0 修改了格式化数据(分帧)的方式,以及客户端与服务器间传输这些数据的方式。这两点统帅全局,通过新的组帧机制向我们的应用隐藏了所有复杂性。换句话说,所有原来的应用都可以不必修改而在新协议运行。
#### 设计和技术目标
HTTP/1.x 的设计初衷主要是实现要简单:HTTP/0.9 只用一行协议就启动了万维网;HTTP/1.0 则是对流行的 0.9 扩展的一个正式说明;HTTP/1.1 则是 IETF 的一份官方标准。因此,HTTP 0.9~1.x 只描述了现实是怎么一回事:HTTP 是应用最广泛、采用最多的一个互联网应用协议。
然而,实现简单是以牺牲应用性能为代价的,而这正是 HTTP/2.0 要致力于解决的:HTTP/2.0 通过支持首部字段压缩和在同一个连接上发送多个并发消息,让应用更有效地利用网络资源,减少感知的延迟时间。而且,它还支持服务器到客户端的主动推送机制。
##### 二进制分帧层
HTTP/2.0 性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。
![](https://i.loli.net/2019/06/05/5cf730c6bc34987526.png)
这里所谓的层,指的是位于套接字接口与应用可见的高层 HTTP API 之间的一个新机制:HTTP 的语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。HTTP/1.x 以换行符作为纯文本的分隔符,而 HTTP/2.0 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。
##### 流、消息和帧
新的二进制分帧机制改变了客户端与服务器之间交互数据的格式,为了说明这个过程,我们需要了解 HTTP/2.0 两个新的概念:
* 流
已建立的连接上的双向字节流。
* 消息
与逻辑消息对应的完整的一系列数据帧。
* 帧
HTTP/2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。
![](https://i.loli.net/2019/06/05/5cf7343f8610140755.png)
所有 HTTP/2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一个或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
这简简单单的几句话里浓缩了大量的信息,要理解 HTTP/2.0,就必须理解流、消息和帧这几个基础概念:
1. 所有通信都在一个 TCP 连接上完成
2. 流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2 ~N)
3. 消息是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成
4. 帧是最小的通信单位,承载着特定类型的数据,如 HTTP 首部、负荷,等等
简言之,HTTP/2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应的,很多流可以并行的在同一个 TCP 连接上交换消息。
##### 多向请求与响应
在 HTTP/1.x 中,如果客户端想发送多个并行的请求以及改进性能,那么必须使用多个 TCP 连接。这是 HTTP/1.x 交互模型的直接结果,该模型会保证每个连接每次只交互一个响应(多个响应必须排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。
HTTP/2.0 中新的二进制分帧层突破了这些限制,实现了多向请求和响应:客户端和服务器可以把 HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。
![](https://i.loli.net/2019/06/05/5cf742f19abea24700.png)
上图中包含了同一个连接上多个传输中的数据流:客户端正在向服务器传输一个 DATA 帧(stream 5),与此同时,服务器正向客户端乱序发送 stream 1 和 stream 3 的一系列帧。此时,一个连接上有 3 个请求/响应并行交换!
把 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP/2.0 最重要的一项增强。事实上,这个机制会在整个 Web 技术栈中引发一系列连锁反应,从而带来巨大的性能提升,因为:
* 可以并行交错的发送请求,请求之间互不影响
* 可以并行交错的发送响应,响应之间互不干扰
* 只使用一个连接即可并行发送多个请求和响应
* 消除不必要的延迟,从而减少页面加载的时间
* 不必再为绕过 HTTP/1.x 限制而多做很多工作
总之,HTTP/2.0 的二进制分帧机制解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,就是应用速度更快,开发更简单,部署成本更低。
> 支持多向请求与响应,可以省掉针对 HTTP/1.x 限制所费的那些脑筋和工作,比如拼接文件、图片精灵、域名分区。类似的,通过减少 TCP 连接的数量,HTTP/2.0 也会减少客户端和服务器的 CPU 及内存占用。
##### 请求优先级
把 HTTP 消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,进一步提升性能。为了做到这一点,每个流都可以带一个 31 比特的优先值:
* 0 表示最高优先级
* 2^31 - 1 表示最低优先级
有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。具体来讲,服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。
##### 服务器推送
HTTP/2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确的请求。
![](https://i.loli.net/2019/06/05/5cf7598f14b9933893.png)
建立 HTTP/2.0 连接后,客户端与服务器交换 SETTINGS 帧,借此可以限定双向并发的流的最大数量。因此,客户端可以限定推送流的数量,或者通过把这个值设置为 0 而完全禁用服务器推送。
为什么需要这样一个机制呢?通常的 Web 应用都由几十个资源组成,客户端需要分析服务器提供的文档才能逐个找到它们。那为什么不让服务器提前就把这些资源推送给客户端,从而减少额外的时间延迟呢?服务器已经知道客户端下一步要请求什么资源了,这时候服务器推送即可派上用场。上篇文章所说的 “嵌入资源” 也可以算作服务器推送。把资源直接插入到文档中,就是把资源直接推送给客户端,而无需客户端请求。在 HTTP/2.0 中,唯一的不同就是把这个过程从应用中拿出来,放到 HTTP 协议本身来实现,而且还带来了如下好处:
* 客户端可以缓存推送过来的资源
* 客户端可以拒绝推送过来的资源
* 推送资源可以由不同的页面共享
* 服务器可以按照优先级推送资源
最后还有一点,就是推送的资源将直接进入客户端缓存,就像客户端请求了似的。不存在客户端 API 或 JavaScript 回调方法等通知机制,可以用于确定资源何时到达。整个过程对运行在浏览器中的 Web 应用来说好像根本不存在。
##### 首部压缩
HTTP 的每一次通信都会携带一组首部,用于描述传输的资源及其属性。在 HTTP/1.x 中,这些元数据都是以纯文本形式发送的,通常会给每个请求增加 500~800 字节的负荷。如果算上 HTTP Cookie,增加的负荷通常会达到上千字节。为减少这些开销并提升性能,HTTP/2.0 会压缩首部元数据:
* HTTP/2.0 在客户端和服务器端使用 “首部表” 来跟踪和存储之前发送的键值对,对于相同的数据,不在通过每次请求和响应发送。
* 首部表在 HTTP/2.0 的连接存续期内始终存在,由客户端和服务器共同渐进的更新。
* 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值
于是,HTTP/2.0 连接的两端都知道已经发送了哪些首部,这些首部的值是什么,从而可以针对之前的数据只编码发送差异数据。
![](https://i.loli.net/2019/06/05/5cf78307e362058386.png)
请求与响应首部的定义在 HTTP/2.0 中基本没有改变,只是所有首部键必须全部小写,而且请求行要独立为 :method、:scheme、:host 和 :path 等这些键值对。
在前面的例子中,第二次请求只需要发送变化了的路径首部(:path),其他首部没有变化,不用再发送了。这样就可以避免传输冗余的首部,从而显著减少每个请求的开销。
#### 二进制分帧层简介
HTTP/2.0 的根本改进还是新增的长度前置的二进制分帧层。与 HTTP/1.x 使用换行符分隔纯文本不同,二进制分帧层更加简洁,通过代码处理起来更简单也更有效。
建立了 HTTP/2.0 连接后,客户端与服务器会通过交换帧来通信,帧是基于这个新协议通信的最小单位。所有帧都共享一个 8 字节的首部,如下图所示,其中包含帧的长度、类型、标志,还有一个保留位和一个 31 位的流标识符。
![](https://i.loli.net/2019/06/05/5cf79db949a0866805.png)
* 16 位的长度前缀意味着一帧大约可以携带 64 KB 数据,不包括 8 字节首部
* 8 位的类型字段决定如何解释帧其余部分的内容
* 8 位的标志字段允许不同的帧类型定义特定于帧的消息标志
* 1 位的保留字段始终置为 0
* 31 位的流标识符唯一标识 HTTP/2.0 的流
知道了帧类型,解析器就知道该如何解析帧的其余内容了,HTTP/2.0 规定了如下帧类型:
* DATA:用于传输 HTTP 消息体
* HEADERS:用于传输关于流的额外的首部字段
* PRIORITY:用于指定或重新指定引用资源的优先级
* RST_STREAM:用于通知流的非正常终止
* SETTINGS:用于通知两端通信方式的配置数据
* PUSH_PROMISE:用于发出创建流和服务器引用资源的要约
* PING:用于计算往返时间,执行 “活性” 检查
* GOAWAY:用于通知对端停止在当前连接中创建流
* WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制
* CONTINUATION:用于继续一系列首部块片段
服务器可以利用 GOAWAY 类型的帧告诉客户端要处理的最后一个流的 ID,从而消除一些请求竞争,而且浏览器也可以据此智能的重试或取消 “悬着的” 请求。这也是保证复用连接安全的一个重要和必要的功能。

@ -0,0 +1,173 @@
---
UDP 的构成
---
#### 目录
1. 前言
2. 无协议服务
3. UDP 与网络地址转换器
* 连接状态超时
* NAT 穿透
* STUN、TURN 与 ICE
4. 针对 UDP 的优化建议
#### 前言
1980 年 8月,紧随 TCP/IP 之后,UDP(用户数据报协议)被 John Postel 加入了核心网络协议套件。UDP 的主要功能和亮点不在于它引入了什么特性,而在于它忽略了哪些特性。UDP 经常被称为无(Null)协议,RFC 768 描述了其运作机制,全文完全可以写在一张餐巾纸上。
> 数据报
>
> 一个完整、独立的数据实体,携带着从源节点到目的地节点的足够信息,对这些节点间之前的数据交换和传输网络没有任何依赖。
数据报(datagram)和分组(packet)是两个经常被人混用的词,实际上它们还是有区别的。分组可以用来指代任何格式化的数据块,而数据报则通常只用来描述那些通过不可靠的服务传输的分组,既不保证送达,也不发送失败通知。正因为如此,很多场合下人们都把 UDP 中 User 的 U 改成 Unreliable(不可靠)的 U,于是 UDP 就成了 “不可靠数据报协议”。这也是为什么把 UDP 分组称为数据报更为恰当的原因。
关于 UDP 的应用,最广为人知同时也是所有浏览器和因特网应用都赖以运作的,就是 DNS。DNS 负责把主机名转换成 IP 地址。可是,尽管浏览器有赖于 UDP,但这个协议以前从未被看成网页和应用的关键传输机制。HTTP 并未规定要使用 TCP,但现实中所有 HTTP 实现都是用 TCP。
不过,这都是过去的事了。IETF 和 W3C 工作组共同制定了一套新 API --- WebRTC(Web 实时通信)。WebRTC 着眼于在浏览器中通过 UDP 实现原生的语音和视频实时通信,以及其他形式的 P2P(端到端)通信。正是因为 WebRTC 的出现,UDP 作为浏览器中重要传输机制的地位才得以突显,而且还有了浏览器 API!
#### 无协议服务
要理解为什么 UDP 被人称作 “无协议”,必须从作为 TCP 和 UDP 下一层的 IP 协议说起。
IP 层的主要任务就是按照地址从源主机向目标主机发送数据报。为此,消息会被封装在一个 IP 分组内,其中载明了源地址和目标地址,以及其他一些路由参数。注意,数据报这个词暗示了一个重要的信息:IP 层不保证消息可靠的交互,也不发送失败通知,实际上是把底层网络的不可靠性直接暴露给了上一层。如果某个路由节点因为网络阻塞、负载过高或其他原因而删除了 IP 分组,那么在必要的情况下,IP 的上一层协议要负责检测、恢复和重发数据。
下面看一下 IPv4 首部(20 字节):
![](https://i.loli.net/2019/06/24/5d103b3bccf2e69399.png)
UDP 协议会用自己的分组结构(如下图)封装用户消息,它只增加了 4 个字段:源端口、目标端口、分组长度和效验和。这样,当 IP 把分组送达目标主机时,主机能够拆开 UDP 分组,根据目标端口找到目标应用程序,然后再把消息发送过去。仅此而已。
下面就是 UDP 首部(8 字节):
![](https://i.loli.net/2019/06/24/5d103c4d2b2fb14310.png)
事实上,UDP 数据报中的源端口和效验和字段都是可选的。IP 分组的首部也有效验和,应用程序可以忽略 UDP 效验和。也就是说,所有错误检测和错误纠正工作都可以委托给上层的应用程序。说到底,UDP 仅仅是在 IP 层之上通过嵌入应用程序的源端口和目标端口,提供了一个 “应用程序多路复用” 机制。明白了这一点,就可以总结一下 UDP 的无服务是怎么回事了。
* 不保证消息交互
不确认,不重传,无超时。
* 不保证交互顺序
不设置包序号,不重拍,不会发生队首阻塞。
* 不跟踪连接状态
不必建立连接或重启状态机。
* 不需要拥塞控制
不内置客户端或网络反馈机制。
TCP 是一个面向字节流的协议,能够以多个分组形式发送应用程序消息,且对分组中的消息范围没有任何明确限制。因此,连接的两端存在一个连接状态,每个分组都有序号,丢失还要重发,并且要按顺序交互。相对来说,UDP 数据报有明确的限制:数据报必须封装在 IP 分组中,应用程序必须读取完整的消息。换句话说,数据报不能分片。
UDP 是一个简单、无状态的协议,适合作为其他上层应用协议的辅助。实际上,这个协议的所有决定都需要由上层的应用程序作出。
#### UDP 与网络地址转换器
令人遗憾的是,IPv4 地址只有 32 位长,因而最多只能提供 42.9 亿个唯一 IP 地址。1990 年代初,互联网上的主机数量呈指数级增长,但不可能所有主机都分配一个唯一的 IP 地址。1994 年,作为解决 IPv4 地址即将耗尽的一个临时性方案,IP 网络地址转换器(NAT)规范出台了。
建议的 IP 重用方案就是在网络边缘加入 NAT 设备,每个 NAT 设备负责维护一个表,表中包含本地 IP 和端口到全球唯一(外网)IP 和端口的映射。这样,NAT 设备背后的 IP 地址空间就可以在各种不同的网络中得到重用,从而解决地址耗尽问题。
IP 地址网络转换器:
![](https://i.loli.net/2019/06/24/5d104178654ce82237.png)
然而,这个临时性的方案居然就那么一直沿用了下来。新增的 NAT 设备不仅立竿见影的解决了地址耗尽问题,而且还迅速成为很多公司及家庭代理和路由器、安全装置、防火墙,以及其他很多硬件和软件设备中的内置组件。NAT 不再是个临时性方案,它已经成了因特网基础设施的一个组成部分。
作为监管全球 IP 地址分配的机构,IANA 为私有网络保留了三段 IP 地址,这些 IP 地址经常可以在 NAT 设备后面的内网中看到。
保留的 IP 地址范围:
| IP 地址范围 | 地址数量 |
| ----------------------------- | ---------- |
| 10.0.0.0 - 10.255.255.255 | 16 777 216 |
| 172.16.0.0 - 172.31.255.255 | 1 048 576 |
| 192.168.0.0 - 192.168.255.255 | 65 536 |
其中一段 IP 地址是不是很眼熟?在你的局域网中,路由器给你的计算机分配的 IP 地址很可能位于其中一段。这个地址就是你在内网中的私有地址。在需要与外网通信时,NAT 设备会将它们转换成外网地址。
为防止路由器错误和引起不必要的麻烦,不允许给外网计算机分配这些保留的私有地址。
##### 连接状态超时
NAT 转换的问题(至少对于 UDP 而言)在于必须维护一份精确的路由表才能保证数据转发。NAT 设备依赖连接状态,而 UDP 没有状态。这种根本上的错配是很多 UDP 数据报传输问题的总根源。况且,客户端前面有很多个 NAT 设备的情况也不鲜见,问题由此进一步恶化了。
每个 TCP 连接都有一个设计周密的协议状态机,从握手开始,然后传输应用数据,最后通过明确的信号确认关闭连接。在这种设计下,路由设备可以监控连接状态,根据情况创建或删除路由表中的条目。而 UDP 呢,没有握手,没有连接终止,实际根本没有可监控的连接状态机。
发送出站的 UDP 不费事,但路由响应却需要转换表中有一个条目能告诉我们本地目标主机的 IP 和端口。因此,转换器必须保存每个 UDP 流的状态,而 UDP 自身却没有状态。
更糟糕的是,NAT 设备还被赋予了删除转换记录的责任,但由于 UDP 没有连接终止确认环节,任何一端随时都可以停止传输数据报,而不必发送通告。为解决这个问题,UDP 路由记录会定时过期。定时多长?没有规定,完全取决于转换器的制造商、型号、版本和配置。因此,对于较长时间的 UDP 通信,有一个事实上的最佳做法,即引入一个双向 keep-alive 分组,周期性的重置传输路径上所有 NAT 设备中转换记录的计时器。
##### NAT 穿透
不可预测的连接状态处理是 NAT 设备带来的一个严重问题,但更为严重的则是很多应用程序根本不能建立 UDP 连接。尤其是 P2P 应用程序,涉及 VoIP、游戏和文件共享等,它们客户端与服务器经常需要角色互换,以实现端到端的双向通信。
NAT 带来的第一个问题,就是内部客户端不知道外网 IP 地址,制只知道内网 IP 地址。NAT 负责重写每个 UDP 分组中的源端口、地址,以及 IP 分组中的源 IP 地址。如果客户端在应用数据中以其内网 IP 地址与外网主机通信,必然连接失败。所谓的 “透明” 转换因此也就成了一句空话,如果应用程序想与私有网络外部的主机通信,那么它首先必须知道自己的外网 IP 地址。
然鹅,知道外网 IP 地址还不是实现 UDP 传输的充分条件。任何到达 NAT 设备外网 IP 的分组还必须有一个目标端口,而且 NAT 转换表中也要有一个条目可以将其转换为内部主机的 IP 地址和端口号。如果没有这个条目(通常是从外网传数据进来),那到达地分组就会被删除。此时的 NAT 设备就像一个分组过滤器,除非用户通过端口转发或类似机制配置过,否则它无法确定将分组发送给哪台内部主机。
由于没有映射规则,入站分组直接被删除:
![](https://i.loli.net/2019/06/24/5d1062efd7abb51936.png)
需要注意的是,上述行为对客户端应用程序不是问题。客户端应用程序基于内部网络实现交互,会在交互期间建立必要的转换记录。不过,如果隔着 NAT 设备,那客户端(作为服务器)处理来自 P2P 应用程序(VoIP、游戏、文件共享)的入站连接时,就必须面对 NAT 穿透问题。
为解决 UDP 与 NAT 的这种不搭配,人们发明了很多穿透技术(TURN、STUN、ICE),用于在 UDP 主机之间建立端对端的连接。
##### STUN、TURN 与 ICE
STUN 是一个协议,可以让应用程序发现网络中的地址转换器,发现之后进一步取得为当前连接分配的外网 IP 地址和端口。为此,这个协议需要一个已知的第三方 STUN 服务器支持,该服务器必须架设在公网上。
STUN 查询外网 IP 地址和端口:
![](https://i.loli.net/2019/06/24/5d10680a54b8347982.png)
假设 STUN 服务器的 IP 地址已知(通过 DNS 查找或手工指定),应用程序首先向 STUN 服务器发送一个绑定请求。然后,STUN 服务器返回一个响应,其中包含在外网中代表客户端的 IP 地址和端口号。这种简单的方式解决了前面讨论的一些问题:
* 应用程序可以获得外网 IP 和端口,并利用这些信息与对端通信
* 发送到 STUN 服务器的出站绑定请求将在通信要经过的 NAT 中建立路由条目,使得到达该 IP 和端口的入站分组可以找到内网中的应用程序。
* STUN 协议定义了一个简单的 keep-alive 探测机制,可以保证 NAT 路由条目不超时
有了这个机制,两台主机端需要通过 UDP 通信时,它们首先都会向各自的 STUN 服务器发送绑定请求,然后分别使用响应中的外网 IP 地址和端口号交换数据。
但在实际应用中,STUN 并不能适应所有类型的 NAT 和网络配置。不仅如此,某些情况下 UDP 还会被防火墙或其他网络设备完全屏蔽。这种情况在很多企业网很常见。为解决这个问题,在 STUN 失败的情况下,我们还可以使用 TURN 协议作为后备。TURN 可以在最坏的情况下跳过 UDP 而切换到 TCP。
TURN 中的关键词当然是中继。这个协议依赖于外网中继设备在两端间传递数据。
TURN 中继服务器:
![](https://i.loli.net/2019/06/24/5d106ad8c7f7369476.png)
* 两端都要向同一台 TURN 服务器发送分配请求未建立连接,然后在进行权限协商
* 协商完毕,两端都把数据发送到 TURN 服务器,再有 TURN 服务器转发,从而实现通信
很明显,这就不再是端对端的数据交换了!TURN 是在任何网络中为两端提供连接的最可靠方式,但运维 TURN 服务器的投入也很大。至少,为满足传输数据的需要,中继设备的容量必须足够大。因此,最好在其他直连手段都失败的情况下,在使用 TURN。
##### ICE
构建高效的 NAT 穿透方案可不容易。好在,我们还有 ICE 协议。ICE 规定了一套方法,致力于在通信各端之间建立一条最有效的通信:能直连就直连,必要时 STUN 协商,再不行使用 TURN。
ICE 先后尝试直连、STUN 和 TURN:
![](https://i.loli.net/2019/06/24/5d106e15a20d534505.png)
#### 针对 UDP 的优化建议
UDP 是一个简单常用的协议,经常用于引导其他传输协议。事实上,UDP 的特色在于它所省略的那些功能:连接状态、握手、重发、重组、重排、拥塞控制、拥塞预防、流量控制,甚至可选的错误检测,统统没有。这个面向消息的最简单的传输层在提供灵活性的同时,也给实现者带来了麻烦。你的应用程序很可能需要从头实现上诉几个或大部分功能,而且每项功能都必须保证与网络中的其他主机和协议和谐共存。
与内置流量和拥塞控制以及拥塞预防的 TCP 不同,UDP 应用程序必须自己实现这些机制。拥塞处理做的不到位的 UDP 应用程序很容易堵塞网络,造成网络性能下降,严重时还会导致网络拥塞崩溃。如果你想在自己的应用程序中使用 UDP,务必要认真研究和学习当下的最佳实践和建议。RFC 5405 就是这么一份文档,它对设计单播 UDP 应用程序给出了很多设计建议,简述如下:
* 应用程序必须容忍各种因特网路径条件
* 应用程序应该控制传输速度
* 应用程序应该对所有流量进行拥塞控制
* 应用程序应该使用与 TCP 相近的带宽
* 应用程序应该准备基于丢包的重发计数器
* 应用程序应该不发送大于路径 MTU 的数据报
* 应用程序应该处理数据报丢失、重复和重排
* 应用程序应该足够稳定以支持两分钟以上的交付延迟
* 应用程序应该支持 IPv4 UDP 校验和,必须支持 IPv6 校验和
* 应用程序可以在需要时使用 keep-alive(最小间隔 15 秒)
很高兴的告诉大家:WebRTC 就是符合这些要求的框架!

@ -6,6 +6,11 @@ Web 性能优化最佳实践
1. 前言
2. 在客户端缓存资源
3. 压缩传输的数据
4. 消除不必要的请求字节
5. 并行处理请求和响应
6. 针对 HTTP/1.1 的优化建议
7. 针对 HTTP/2.0 的优化建议
#### 前言

@ -1,62 +1,62 @@
---
针对浏览器的优化建议
---
#### 前言
浏览器可远远不止一个网络套接字管理器那么简单,性能可以说是每个浏览器开发商的核心卖点,既然性能如此重要,那浏览器越来越聪明也就毫不奇怪了。预解析可能的 DNS 查询、预连接可能的目标、预取得和优先取得重要资源,这些都是浏览器变聪明的标志。
#### 优化建议
可行的优化手段会因浏览器而异,但从核心优化策略来说,可以宽泛的分为两类:
* 基于文档的优化
熟悉网络协议,了解文档、CSS 和 JavaScript 解析管道,发现和优先安排关键网络资源,尽早分配请求并取得页面,使其尽快达到可交互的状态。主要方法是优先获取资源、提前解析等。
* 推测性优化
浏览器可以学习用户的导航模式,执行推测性优化,尝试预测用户的下一次操作,然后,预先解析 DNS、预先连接可能的目标。
好消息是,所有的这些优化都是由浏览器替我们自动完成的,经常可以节省几百 ms 的网络延迟。既然如此,那理解这些优化背后的原理就至关重要了,这样才能利用浏览器的这些特性,提升应用性能。大多数浏览器都利用了如下四种技术:
1. 资源预取和排定优先次序
文档、CSS 和 JavaScript 解析器可以与网络协议层沟通,声明各种资源的优先级;初始渲染必需的阻塞资源具有最高优先级,而低优先级的请求可能会被临时保存在队列中。
2. DNS 预解析
对可能的域名进行提前解析,避免将来 HTTP 请求时的 DNS 延迟。预解析可以通过学习导航历史、用户的鼠标悬停,或其他页面信号来触发。
3. TCP 预连接
DNS 解析之后,浏览器可以根据预测的 HTTP 请求,推测性的打开 TCP 连接。如果猜对的话,则可以节省一次完整的往返(TCP 握手)时间。
4. 页面预渲染
某些浏览器可以让我们提升下一个可能的目标,从而在隐藏的标签页中预先渲染整个页面。这样,当用户真的触发导航时,就能立即切换过来。
从外部看,现代浏览器的网络协议实现以简单的资源获取机制的面目示人,而从内部来说,它又极为复杂精密,为了解如何优化性能,非常值得深入钻研。那么,在探寻的过程中,我们怎么利用浏览器的这些机制呢?首先,要密切关注每个页面的结构和交互:
* CSS 和 JavaScript 等重要资源应该尽早在文档中出现
* 应该尽早交互 CSS,从而解除渲染阻塞并让 JavaScript 执行
* 非关键性 JavaScript 应该推迟,以避免阻塞 DOM 和 CSSOM 构建
* HTML 文档由解析器递增解析,从而保证文档可以间歇性发送,以求得最佳性能
除了优化页面结构,还可以在文档中嵌入提示,以触发浏览器为我们采用其他优化机制:
```html
<link rel="dns-prefetch" href="//hostname_to_resolve.com">
<link rel="subresource" href="/javascript/myapp.js">
<link rel="prefetch" href="/images/big.jpeg">
<link rel="prerender" href="//example.org/next_page.html">
```
从上到下,依次是:
1. 预解析特定的域名
2. 预取得页面后面要用到的关键性资源
3. 预取得将来导航要用的资源
4. 根据对用户下一个目标的预测,预渲染特定页面
---
针对浏览器的优化建议
---
#### 前言
浏览器可远远不止一个网络套接字管理器那么简单,性能可以说是每个浏览器开发商的核心卖点,既然性能如此重要,那浏览器越来越聪明也就毫不奇怪了。预解析可能的 DNS 查询、预连接可能的目标、预取得和优先取得重要资源,这些都是浏览器变聪明的标志。
#### 优化建议
可行的优化手段会因浏览器而异,但从核心优化策略来说,可以宽泛的分为两类:
* 基于文档的优化
熟悉网络协议,了解文档、CSS 和 JavaScript 解析管道,发现和优先安排关键网络资源,尽早分配请求并取得页面,使其尽快达到可交互的状态。主要方法是优先获取资源、提前解析等。
* 推测性优化
浏览器可以学习用户的导航模式,执行推测性优化,尝试预测用户的下一次操作,然后,预先解析 DNS、预先连接可能的目标。
好消息是,所有的这些优化都是由浏览器替我们自动完成的,经常可以节省几百 ms 的网络延迟。既然如此,那理解这些优化背后的原理就至关重要了,这样才能利用浏览器的这些特性,提升应用性能。大多数浏览器都利用了如下四种技术:
1. 资源预取和排定优先次序
文档、CSS 和 JavaScript 解析器可以与网络协议层沟通,声明各种资源的优先级;初始渲染必需的阻塞资源具有最高优先级,而低优先级的请求可能会被临时保存在队列中。
2. DNS 预解析
对可能的域名进行提前解析,避免将来 HTTP 请求时的 DNS 延迟。预解析可以通过学习导航历史、用户的鼠标悬停,或其他页面信号来触发。
3. TCP 预连接
DNS 解析之后,浏览器可以根据预测的 HTTP 请求,推测性的打开 TCP 连接。如果猜对的话,则可以节省一次完整的往返(TCP 握手)时间。
4. 页面预渲染
某些浏览器可以让我们提升下一个可能的目标,从而在隐藏的标签页中预先渲染整个页面。这样,当用户真的触发导航时,就能立即切换过来。
从外部看,现代浏览器的网络协议实现以简单的资源获取机制的面目示人,而从内部来说,它又极为复杂精密,为了解如何优化性能,非常值得深入钻研。那么,在探寻的过程中,我们怎么利用浏览器的这些机制呢?首先,要密切关注每个页面的结构和交互:
* CSS 和 JavaScript 等重要资源应该尽早在文档中出现
* 应该尽早交互 CSS,从而解除渲染阻塞并让 JavaScript 执行
* 非关键性 JavaScript 应该推迟,以避免阻塞 DOM 和 CSSOM 构建
* HTML 文档由解析器递增解析,从而保证文档可以间歇性发送,以求得最佳性能
除了优化页面结构,还可以在文档中嵌入提示,以触发浏览器为我们采用其他优化机制:
```html
<link rel="dns-prefetch" href="//hostname_to_resolve.com">
<link rel="subresource" href="/javascript/myapp.js">
<link rel="prefetch" href="/images/big.jpeg">
<link rel="prerender" href="//example.org/next_page.html">
```
从上到下,依次是:
1. 预解析特定的域名
2. 预取得页面后面要用到的关键性资源
3. 预取得将来导航要用的资源
4. 根据对用户下一个目标的预测,预渲染特定页面
这里的每一个提示都会触发一个推测性优化机制。浏览器虽然不能保证落实,但可以利用这些提示优化加载策略。可惜的是,并非所有浏览器都支持这些提示,不过,如果它们不支持,也只会把提示当做空操作,有益无害。因此,一定要尽早可能利用这些字段。

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Loading…
Cancel
Save