• Home
  • Archives
  Evan的博客
  • Home
  • Archives
  • 面试
  • 原理笔记
  • 项目实践
  • 其他

从 HTTP 发展历程重学计算机网络

2020/10/18 posted in  原理笔记

基础概念

在讲 HTTP 之前,先来简单介绍一下什么是网络协议模型,什么是 OSI 七层网络协议,什么是 TCP/IP 四层协议

OSI 七层网络协议

OSI 全称 Open Systems Interconnection model,是一种互联模型。它的目的是通过一种规范的标准通信协议,实现不同系统之间的通信互连,实现不同操作系统的互操作性。他的组成由下表所示

模型层 常见协议
应用层 HTTP,TFTP,FTP,NFS,SMTP,DNS
表示层 Telnet
会话层 RPC,SDP,SOCKS,PPTP
传输层 TCP,UDP
网络层 IP,ICMP,RARP
数据链路层 FDDI,Ethernet,Arpanet,PDN,SLIP,PPP
物理层 IEEE 802.1A,IEEE 802.2,IEEE 802.11

TCP/IP 四层协议

OSI 是一种学术上的理论模型,而实际应用中一般是以 TCP/IP 四层协议模型居多。它将应用层,表示层,会话层统一在同一个层级内;将数据链路层和物理层统一在同一层级内(也有一些模型不统一物理层,变成五层模型)

模型层 常见协议
应用层 HTTP,TFTP,FTP,NFS,SMTP,DNS,RPC,SDP,SOCKS
传输层 TCP,UDP
网络层 IP,ICMP,RARP
物理层 IEEE 802.1A,IEEE 802.2,IEEE 802.11

2018041112053246

什么是 HTTP

从上面的协议模型中,可以看出 HTTP 是一种应用层的协议。它基于 TCP/IP 的通信协议,制定了客户端与服务端之间的通讯规则

发展历程

HTTP 0.9

1991年 HTTP 问世,在这个阶段的 HTTP 是极为简陋的,只支持 GET 请求。返回也同样简单,只支持 HTML 文本。

这个阶段 HTTP 就已经是无状态的设计理念了,一个请求响应结束后,连接就会被释放。这也是后来的 cookies 和 session 的诞生的理由,同样的后来也成为了 HTTP 性能上的一个瓶颈,后面讲队头阻塞的时候会细说。

HTTP 1.0

1996年 HTTP 1.0 正式被推出。这个版本的 HTTP 协议增加了 头域 的概念,也就是我们熟知的 Request Header 和 Respond Header。此外 HTTP 1.0 新增了一些特性:

  • 新增了 POST 和 HEAD 方法
  • 引入 Content-Type 的概念,响应内容不再局限于文本
  • 支持长连接 Connection: Keep-Alive ,但默认不开启
  • 支持不同编码格式的文件传输如 Content-Encoding: gzip

HTTP 1.0 的升级主要是围绕 丰富功能 和 性能优化 两个方面的。比如支持更多的请求和响应类型,比如支持长连接复用同一个 TCP,比如支持压缩格式提高传输速度等

HTTP 1.1

在 HTTP 1.0 推出后没几个月,HTTP 1.1 就紧跟着问世了。这是一个相对稳定的版本,直到今天也有许多的应用采用 HTTP 1.1 协议进行前后端信息交互。主要做了以下更新:

  • 新增 OPTIONS、PUT、DELETE、TRACE、CONNECT 等多种方法
  • 默认使用长连接
  • 引入了 Pipeline 的概念,但因为某些原因并未得到广泛使用,后面细说
  • 增加了 Range 头,支持范围请求,也是断点续传的基础
  • 补充了更为完善的 Cache-control 做缓存管理

HTTP 2

2015 年 IETF(互联网工程任务组)正式宣布 HTTP 2 发布。HTTP 1.1 在坚挺了近十年之后,迎来了一次重大变革。

  • 二进制传输
    • 帧是最小的通信单位,每个帧有一个头部,用来标识它所属的流
    • 一个或多个帧构成一条消息,一条消息对应的是 HTTP 1.1 中的一个请求或者响应
    • 一个或多个消息构成流,流是双向通信的,它既可以是客户端发往服务器的,也可以是服务器发往客户端。一个请求与其对应的响应(即请求和响应消息)在同一个流上传输

HTTP 1.1 中,客户端与服务端之间的每条通信,都是一个新的 TCP 连接,而 HTTP 2 中客户端与服务端之间只有一条 TCP 连接,所有的通信都在这个长连接上完成,即在这条连接上承载双向数据流。

之所以这么做是因为每次开 TCP 连接都需要耗费一定的性能,复用一条连接就能让性能大大提高。此外浏览器对 TCP 的连接数是有上限限制的,比如 chrome 的最大连接数是 6 个,如果超过了将会等待前面的 TCP 连接释放后才能继续开启更多的 TCP 连接。

每个数据流都有一个唯一的标识和一个可选的优先级,流是消息的载体,可以理解为是 HTTP 1.1 中请求和响应的载体。来自不同数据流的帧可以交错发送,然后送达之后再根据帧头部的标识位找到其对应的流,重组。

总结就是,每个 TCP 连接有若干个流,每个流中有多组消息对(即请求与响应对),每个消息又是由若干个帧组成的。

1579689227454_3b7ec6e12345ad4f20ed308d8d23e991

  • 服务端推送

服务端除了响应客户端的某个请求之外,还能向客户端额外推送更多的内容

  • 头部压缩

引入 HPACK 技术对 HTTP 头进行压缩,减小体积

HTTP 3

HTTP 3 的前身是 QUIC 协议。2018年底的时候,IETF 正式将 HTTP Over QUIC 命名为 HTTP 3。QUIC 全称是 Quick UDP Internet Connection,是谷歌提出的一个基于 UDP 的低延迟多并发的传输协议。这也意味着 HTTP 3 将弃用 TCP,转向 UDP 阵营。

1580000162089_3c14d73667b9fbc1d6836d211c8ede4f

  • 采用基于 UDP 的方案

可能很多人会有疑惑,采用无序的 UDP,那怎么保证数据的可靠性呢?答案在于从应用层重新控制排序。HTTP 2 是在传输层利用 TCP 有序的特性来提供可靠的数据传输。而 HTTP 3 则是将传输与可靠性解耦,传输层的 UDP 只负责传输,传输之外的拥塞控制、重传机制等放在应用层处理。到达应用层后,用额外的内存资源来处理乱序,其实这是一个以空间换时间的过程。

  • 0RTT

RTT 是 Round-Trip Time 往返延迟的缩写。0RTT 是只发起方第一个数据包里面便可以携带有效的业务数据。一般情况下基于 TCP 的 HTTPS 至少需要 2 个 RTT。第一个 RTT 是 TCP 的三次握手。然后 HTTPS 的 TLS 握手也需要 1-2 个 RTT。每次 RTT 大概100ms,也就是说整个建立连接的过程需要大概需要消耗掉 200-300 毫秒。

  • 连接迁移

IP 是 TCP 连接四元组(srcIP,srcPort,deskIP,deskPort)之一。这意味着 IP 切换的时候肯定要重连 TCP。比如 4g 切到 wifi 网络的时候。QUIC 通过一个 64 位的 Connection ID 来标识一个连接,无论 IP,Port 如何变化,只要这 64 位 ID 不变,则连接一直有效,无需进行重连,对上层业务也不会有感知。

各路神仙大战性能优化

从 HTTP 的发展史不难看出,其实大多数变革都是围绕着 快 这个字展开的。性能瓶颈是网络传输中最常遇到的问题,也是用户感知最明显的问题。这也可以说是 HTTP 进化的最大动力。来看一下发展史中关于性能的优化,有哪些重要的转折点:

长连接

长连接分两种,一种是应用层的长连接(HTTP),一种是协议层的长连接(TCP),两者是不一样的,要注意区分。

  • 应用层长连接

HTTP 1 和 HTTP 1.1 里的长连接,都是应用层的长连接。未开启长连接时,HTTP 是一个请求与一个响应一一对应,并且一个请求收到其对应响应的时候,将会关闭此次连接。开启长连接后,将允许在一条 HTTP 链路上处理多个请求响应对。

如果每次请求都需要从头开始,那会是这样的:初始化 DNS 查询,查到目标 IP 后开始 TCP 握手,握手成功之后还要进行 SSL/TLS 握手。这些过程都是很耗费时间的。

  • 协议层长连接

协议层长连接指的是 TCP 的长连接。应用层的 Keep-Alive 更多是体现在应用层面上,而协议层的长连接则更像是一种保活机制。在没有请求的时候,TCP 会定时发送心跳包,确保通信双方的连接仍旧正常。

事实上应用层的长连接也离不开协议层的长连接。HTTP 是建立在 TCP 的基础之上的。而且某些时候,在应用层也是可以做心跳检测保活的。虽然协议层有保活的能力,但是开发者对他的控制并不透明。我们不好去监控和判断 TCP 的心跳是否正常。对一些需要高时效性的应用,还是需要开发者自己做应用层的保活的。

Pipeline

Pipeline 是 HTTP 1.1 提出的概念,它允许在同一个连接上一次性发送多个请求。在 Pipeline 提出之前,一个请求只能等待对应的响应返回后才能开始下一个请求。会做这种阻塞式的设计主要是因为 HTTP 是无状态的。一个响应并不能知道自己对应的是哪个请求,所以最初的时候只能通过阻塞,来实现请求/响应的一一对应。

不过从结果上看,Pipeline 并没有被推广开来。因为它虽然允许一次性发送多个请求,但是接收响应的时候必须依照发送的顺序进行接收,即如果前一个请求在某处遇到了阻塞,即使后面的请求已经处理完毕,仍然需要等待前面请求处理后才能发送。

所以从本质上说,Pipeline 并不能解决队头阻塞的问题,只是从一个地方塞换到了另一个地方塞罢了。这就好比原本在高速入口设立的保证顺序的检查站,现在撤销了,搬到高速出口去检查了😂

转战 UDP

虽说 HTTP 2 通过二进制分帧的方式实现了多路复用,解决了 HTTP 1.1 的队头阻塞的问题,但这也仅仅只解决了应用层的队头阻塞。位于传输层的 TCP 仍然存在队头阻塞的问题。

实际建立连接的过程中,TCP 是通过发送 Segment 的形式去建立连接的。如果有 Segment 阻塞了,TCP 是不会上报到应用层的,也就无法下一步开启 HTTP 连接。

在某些极端情况下,HTTP 2 甚至还不如 HTTP 1.1。因为 HTTP 2 都是复用同一条 TCP 连接的,如果这条链路出现了什么问题,意味着唯一的通路断了。

既然 TCP 是罪魁祸首,那谷歌也是艺高人胆大:“我有个大胆的想法” 。谷歌提出了基于 UDP 的 QUIC 协议,也就是后来的 HTTP 3。众所周知,UDP 是一个非有序的连接,它不管数据的准确性。为了保证 UDP 传输的可靠性,QUIC 引入了一个 Stream Offset 的概念,用来在应用层处理乱序。

结语

计算机网络是个非常广的研究领域,甚至可以作为一个专门的研究方向。要想对网络有个深层次的认识,也不是三天两头一年半载可以做到的。

作为开发者,我们更多是从应用的层面去理解和剖析性能优化点,来提供用户更好的体验。从整个 HTTP 发展史作为入口,能让我们逐步认识网络背后更深一层的原理。有多深谈不上,但是至少知道一个大致的发展方向。比如理清队头阻塞的概念,应用层和协议层的阻塞和长连接又有什么不同等等。

应届前端的逆袭(中) »

Evan的博客

Evan 的博客 - 非典型码农,bug永动机
Instagram Weibo GitHub Email RSS

Categories

面试 原理笔记 项目实践 其他 JS Vue 性能优化 算法 计算机网络

Recent Posts

  • 从 HTTP 发展历程重学计算机网络
  • 应届前端的逆袭(中)
  • 应届前端的逆袭(上)
  • 应届前端的逆袭(下)
  • 前端面经复盘

Copyright © 2015 Powered by MWeb,  Theme used GitHub CSS.