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

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

基础概念

在讲 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 发展史作为入口,能让我们逐步认识网络背后更深一层的原理。有多深谈不上,但是至少知道一个大致的发展方向。比如理清队头阻塞的概念,应用层和协议层的阻塞和长连接又有什么不同等等。

2020/10/18 posted in  原理笔记

应届前端的逆袭(中)

职业规划和选厂建议
2020/10/18 posted in  其他

应届前端的逆袭(上)

这篇文章主要是写给刚毕业,或者刚转行入坑前端的同学,总结了一些经验和学习心得,也算是以自身经历为例子的职业入坑指引吧。上篇主要介绍当前及未来前端发展的趋势,我的日常 workflow 以及高效团队协作的开发配置。文章中涉及到的技术栈和框架不会太深入去讲,主要是介绍一个职业规划和个人成长规划。
2020/10/14 posted in  其他

应届前端的逆袭(下)

假设你的团队 Leader 让你做一个全新的业务,你会怎么做?前端老兵可能不觉得是什么大挑战,但是对大部分新人来说,可能就懵了。每位同学既然能应聘上 “前端” 这一岗位,就肯定有一定的开发能力。没错,你会 `clone` 仓库,你会写业务代码,你会提交你会上线。但问题是,如果没有仓库呢?需要从零开始起项目的时候,又该怎么去折腾呢?只是装完脚手架然后往仓库一推就完事了么?
2020/10/14 posted in  面试

前端面经复盘

这次面试总共花了约一个月,8 月中旬开始,到 9 月中旬。期间共面试 5 家公司:腾讯,字节跳动,百度,富途,转转。面试完之后打算抽空简单写写总结,复盘。
2020/10/11 posted in  面试

大前端相关面试题

近两年大前端相关的话题越来越多。所谓大前端,我的理解是:对前端工程师知识体系中广度的考核。尤其是 node 诞生之后,JS 能干的东西越来越多,简单的如写个页面,复杂的甚至有 NodeOS 这种操作系统的玩意。
2020/10/10 posted in  面试

常见 HTML 面试题

常见的 html 和 css 面试题汇总
2020/10/10 posted in  面试

手撕 Promise

手写 Promise 可谓是 17-19 年间常问的面试题了。虽然这两年问这种题的面试已经不多了,但是熟悉 Promise 的底层原理对自己的 JS 认知有很大的帮助。这篇文章会带大家写一个符合 promise A+ 规范的小型 Promise
2020/10/10 posted in  面试

常用设计模式

这篇文章主要汇总一些在前端中常见的设计模式,不会每一个都展开讲,遇到觉得有必要深入记录的设计模式就会展开细说。
2020/10/10 posted in  面试

强缓存与协商缓存

这篇文章介绍了强缓存与协商缓存,即本地缓存与 HTTP 缓存的异同。
2020/10/10 posted in  面试

面试常见性能问题

总结了一些常见的性能相关的面试题,分享给大家,也供自己复习的时候看。
2020/10/10 posted in  面试

数据结构之图

图是一种各个节点可相互连接的数据结构
2020/10/10 posted in  面试

数据结构之数组

数组是最简单的内存数据结构,一般来说存储同种数据类型,不过 js 允许数组存储不同的数据类型
2020/10/10 posted in  面试

数据结构之栈

栈是一种后进先出(LIFO)的有序的数据结构,新元素在栈顶,旧元素在栈底。可以利用数组来创建栈。
2020/10/10 posted in  面试

数据结构之队列

队列是一种先来先服务,先进先出(FIFO)的数据结构。日常中排队就是最常见的队列。在计算机科学中,打印队列就是一种很常见的队列。
2020/10/10 posted in  面试

数据结构之树

通常我们指的树是二叉搜索树,这种树的特性是每个节点都只有两个子节点,且左子节点比父节点小,右子节点比父节点大。这种特性有助于我们在树中插入、删除和查找元素
2020/10/10 posted in  面试

数据结构之链表

链表是用来存储多个数据的一种结构。链表由节点构成,每个节点是`数据 + 指针`的构成方式。与数组不同,链表在内存里并不是连续放置的,是由指针指向下一个引用地址。这种结构的好处是增删元素不需要大量移动其他元素,只需要断开插入位置的连接,重新拼接新数据节点即可。但也有缺点,就是不能像数组一样通过下标直接定位到一个元素,而是要遍历链表,一节一节地去找到目标元素
2020/10/10 posted in  面试

leetcode 刷题之旅

整理了一些平时看到的算法题,以及部分真题。平时积累提升自己的思维能力,面试前也方便自己复习。
2020/10/10 posted in  面试

常见算法思路

这篇文章介绍了一些常见的算法思路,包括尾递归优化,回溯剪枝,贪心算法等等。目前还没写完,以后会慢慢扩展更多的算法介绍。
2020/10/10 posted in  面试

常见的排序

这篇文章主要介绍常见的几种排序,除了工作中可能用到之外,面试时被问也不会尴尬。
2020/10/10 posted in  面试

» Next Page

Evan的博客

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

Categories

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

Recent Posts

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

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