阅读 85

网络知识

HTTP发展史

HTTP(HyperText Transfer Protocol)超文本传输协议,是互联网上最普遍采用的一种应用协议,也是客户端和服务器之间的公用语言,是现代web的基础。

从1991年协议诞生以来,已经经历过四个版本,分别是http 0.9、http 1.0、http 1.1和http2.0。

互联网的快速发展深刻影响了http的协议升级,不同阶段的http协议都做了不同的性能优化以适应当时的网络需求。

http 0.9

http 0.9是一个非常简单的协议。它只支持get方法,没有首部,设计目标也只是获取纯文本内容的HTML。

http 0.9的主要功能:

  • 客户端/服务器、请求/响应协议
  • ASCII协议,运行于TCP/IP链接之上
  • 设计用来传输超文本文档(HTML)
  • 服务器于客户端之间的连接在每次请求之后都会关闭

http 1.0

随着九十年代互联网的繁荣,http 0.9很多根本性的不足被暴露出来。

1996年,HTTP工作组发布了RFC 1945,解释说明了当时很多http 1.0实现的“公共用法”,但是RFC只是一个参考文档,所以http 1.0并不是一个正式的规范或者互联网标准。

http 1.0 关键的变化:

  • 请求可以由多行首部字段构成
  • 响应对象前面增加了一个响应状态行
  • 响应对象也有自己的由换行符分隔的首部字段
  • 响应对象不局限于超文本
  • 内容编码、字符集支持、认证、缓存、日期格式等

虽然相对于http 0.9来说,1.0是一个巨大的飞跃,但是任然存在很多缺陷。

不能多个请求公用一个tcp连接,缺少强制的host首部,缓存也很简陋是三个比较突出的缺陷。

http 1.1

1999年发布RFC 2616,定义了简历现代web所依赖的标准,所有http 1.1是一个互联网标准协议。

http 1.1是使用非常广泛的协议,现在很多网站还在使用。

相对于http 1.0,它加入了很多重要的性能优化:持久连接、请求管道、字节范围请求、增强的缓存机制、分块编码传输以及传输编码。

持久连接

在http 1.1之前,每个http请求完成以后会自动关闭tcp连接,新的请求会重新开启一个TCP连接。

每一个TCP连接都会存在一些固定的开销,比如三次握手、慢启动等。

http

如上图所示,请求一个html页面,里面包含一个css样式文件,在1.0的协议下面,需要两次TCP连接,造成不必要的开销,当页面的资源不断增加时,这个网络延迟会不断增加。

最简单的处理方法是重用底层的TCP连接。在http 1.1的时候,增加了Connection这个首部字段,设置为Keep-Alive的时候,会重用TCP连接。浏览器默认开始,可以通过设置为false来手动关闭。

缓存机制

http 1.1新增了Cache-Control和Etag这个两个字段来增加缓存策略,替代原来的Expires和Last-Modified这两个字段。

Last-Modified存在的问题

  • Last-Modified标注的是最后修改的时间,只能精确到秒。如果某些文件在一秒内被修改多次的话,它蒋不能准确的标注修改时间
  • 如果某些文件会被定期生成,这些新生成的文件内容并没有修改,但是Last-Modified却改变了,这会导致文件缓存失败
  • 还有可能服务器没有准确获取文件的修改时间,或者与代理服务器的时间不一致,导致缓存失效

Etag可以解决上面的问题,因为Etag是由文件内容和一些必要的文件元素生成的唯一标识符,能更加准确的反应出文件的变动情况。

Expires存在的问题

  • 可能会存在客户端和服务器的时间不一致,导致资源缓存失效。比如资源的缓存时间是两小时,但是客户端的时间比服务端快了三个小时,就会造成资源的缓存失效

Cache-Control可以解决Expires的问题,并且提供更多的缓存选项。缓存时间可以设置max-age来设置,缓存的开始时间是客户端的本地时间。

如果这些缓存字段都存在,那么1.1提供的字段优先级会高于1.0的字段。

HTTP管道

在http 1.0中,多次请求必须严格满足先进先出(FIFO)的队列顺序:发送请求、等待响应完成、再发送客户端队列中的下一个请求,过程如下图

dd

HTTP管道做的优化,可以看成是把FIFO的请求队列从客户端迁移到服务器。

dddd

局限性:队首阻塞

在http 1.1中只能严格串行的返回响应,它不允许一个连接上的多个响应数据交错到达(多路复用),因而一个响应必须完全返回后,下一个响应才会开始传输,这就是HTTP的队首阻塞。

由于存在队首阻塞,所有HTTP管道存在很多的不确定性,很多浏览器都默认禁用这种功能。

使用多个TCP连接

因为http 1.1不支持多路复用,所以浏览器给出的解决方案是并行打开多个TCP连接,每个主机最多支持开个6个连接。

但是也存在一些问题,比如更多的套接字会占用客服端、服务器以及代理的资源,并行的TCP流之间竞争共享的带宽,处理多个套接字导致复杂性增高。

6这个数字的确定是一个多方平衡的结果:数字越大,占用的资源的越多,但是并发性能越好。最后选择6个,算是一个比较安全的数字。

字节范围请求

http 1.1 增加了Range字段,用来描述请求的范围。

类似如 Range: bytes=5001-10000

如果服务器能够正确放回请求的指定范围的资源数据,状态码是206 Partial Conteng;如果无法处理,会返回200,并且把所有资源全部返回。

http 2.0

http 2.0是对http 1.1的又一次优化,它的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段蒋协议开销降至最低,同时增加对请求优先级和服务器推送的支持。

二进制分帧层与多路复用

http 2.0性能增强的核心就在于新增的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务器之间传输。

这个“层”位于应用层和会话层之间,对于之前的http的语义,包括方法、首部等都不受影响,不同的是传输期间对他们的编码方式进行了改变。

在http 1.1中,是以换行符来作为纯文本的分隔符,而http 2.0将数据分割为更小的消息和帧,并采用二进制的格式进行编码。相比于1.1中的纯文本传输,二进制的数据在体积上会更加小,节约流量。

分帧层

http 2.0的二进制分帧层引入了几个新的概念

  • 流:已建立的连接上的双向字节流
  • 消息:与逻辑消息对应的完整的一系列数据帧
  • 帧:http 2.0通信的最小单位,每个帧包含帧首部,只要会有标识出当前帧所属的流

link

流可以看成是一系列的帧,它们构成了单独的HTTP请求和响应。

当客户端发出一个请求,它会开启一个新的流,然后服务器将在这个流上进行回复。因为每个帧都会带有它所属于的那个流的ID,所以最后可以通过流ID将接受的帧进行分组和组合,从而达到多路复用。

帧结构

名称长度描述
Length3字节表示帧负载的长度
Type1字节当前帧类型
Flags1字节具体帧类型的标识
R1位保留位,不要设置,否则可能带来严重的后果
Stream Identifier31位所属流的唯一ID
Frame Payload1字节真是的帧内容,长度是在Length字段中设置的

帧类型

名称ID描述
DATA0x0传输流的核心内容
HEADERS0x1包含HTTP首部,和可选的优先级参数
PRIORITY0x2只是活着更改流的优先级和依赖
RES_STREAM0x3允许一端停止流
SETTINGS0x4协商连接级参数
PUSH_PROMISE0x5提示客户端,服务器要推送数据
PING0x6测试连接可用性和往返时延(RTT)
GOAWAY0x7告诉另一端,当前端已结束
WINDOW_UPDATE0x8协商一端将要接收多少字节(用于流量控制)
CONTINUATION0x9用以扩展HEADERS数据块

请求优先级

通过上面对帧的介绍,可以看出使用PRIORITY帧就可以确定流的优先级。

优先级由31比特的值标识,0的优先级最高。

对于一个页面来说,不同类型的资源所拥有的优先级也不同。css和js文件的优先级最高,一些图片、字体资源的优先级会相对低一点。

所有的现代浏览器都会基于资源的类型以及它在页面中的位置来确定请求的优先级,甚至通过之前的访问来学习优先级模式。

但是请求的优先级顺序也并不能简单的按照资源的重要程度来确定,因为一旦TCP队首阻塞,就会造成后续的资源无法加载,所有需要更优的策略来控制资源的返回,比如不同优先级交错返回。

每个域名一个连接

有了新的分帧机制,http 2.0 不再需要开启多个tcp 连接来实现并行请求了。

TCP连接的减少,使得套接字的管理工作量减少,内存占用也相应的减少,请求慢启动的时间减少,能用更少的时间达到链路的吞吐量峰值。

首部压缩

HTTP的每一次通信都会携带一组首部,用于描述传输的资源和它的一些属性。

在http 1.x中,这些首部信息都是以纯文本的形式发送的,通常会给每个请求增加500~800字节的负荷,如果加上cookie,这个负荷可能会达到上千字节。如果一个简单的get请求,可能首部信息的大小会远远超过数据体的大小;另一种情况是相同的请求,所有的首部信息都要重新传递,造成不必要的性能开销。

http 2.0 使用首部压缩(HPACK)来改善首部的开销。

hpack

  • http 2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送
  • 首部表在http 2.0的连接存续期内是始终存在的,有客户端和服务器端共同渐进的更新
  • 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值
  • 首部表由两张表组成,一个是动态表,一个是静态表。静态表用来存放一些常见的首部键值对,比如:method: get在静态表中的索引为2。静态表的键值对索引是无法改变的,所以动态表的索引都是从62开始的。

服务器推送

http 2.0新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应,除了对最初的请求响应以外,服务器还可以额外向客户端推送资源而无需客户端明确地请求。

tuisong

开启服务器推送的功能后,index.html中的一些资源可以一次性返回给客户端,而不需要重新发送请求,减少了网络延迟。

对于服务器推送的资源客户端可以进行正常的缓存。但是一旦缓存生效,那么即使推送的文件版本进行了更新,浏览器也会优先使用本地缓存。一种解决方法就是只对第一次访问的用户开启服务器推送功能,以后再访问的时候就走正常的页面请求。

是否已经完美?

通过对http 2.0的介绍,感觉现有的协议已经趋于完美,但是还存在一些问题。

一个明显的问题就是丢包问题,一个TCP连接一旦丢包,就会造成队首阻塞。而且http 2.0一个域名只启动一个TCP连接,一旦丢包,会阻塞后续所有的请求,相比于http 1.1的多个TCP连接并发请求,后果要严重的多。

但是这些问题更多的是TCP协议带来的,和HTTP协议自身没有太大的关系,而且HTTP并没有规定必须使用TCP协议,所以对于TCP的优化会是下一个性能突破口。

TCP

下图就是OSI的分层模型,TCP是传输层的协议,负责在不可靠的传输信道上提供可靠的抽象层。

TCP通过各种机制来提供可靠的传输,比如三次握手、丢包重发、按序发送、拥塞控制及避免、数据完整性等。

iso

三次握手

所有TCP连接一开始都要经过三次握手,通过三次握手交换一些必要的数据,握手完成以后就可以进行正常的通信了。

sanci

  • SYN:客户端选择一个随机序列号x,发送给服务端
  • SYN + ACK:服务端对x加1,通过ACK发送给客户端,并随机选择一个序列号y通过SYN发送给客户端
  • ACK:客户端对收到的SYN加一,通过ACK发送给服务端

两种窗口

TCP存在两个窗口,一个是接收窗口(rwnd),一个是拥塞窗口(cwnd)。

接收窗口

为了实现流量控制,TCP连接的每一方都要通告自己的接收窗口,其中包含能够保存数据的缓冲区空间大小信息。

在每一次数据交换的时候,这个值都可能会改变。当窗口变成 0 时,表示接收端不能暂时不能再接收数据了。

存在一种情况,如果接收窗口为0,经过一段时间接收端从高负载中释放出来,但是发送端并不知道是否可以再次发送数据。TCP提供了零窗口探测的机制(Zero window probe),用来向接收端探测,是否可以再次发送数据,它也满足超时重传的策略。

rwnd

拥塞窗口

拥塞窗口指的是在收到对端 ACK 之前自己还能传输的最大 MSS(最大报文长度) 段数。

拥塞窗口初始值等于操作系统的一个变量 initcwnd,最初的值为1,1994年增加为4,最新的initcwnd 默认值等于10。

  • 接收窗口(rwnd)是接收端的限制,是接收端还能接收的数据量大小
  • 拥塞窗口(cwnd)是发送端的限制,是发送端在还未收到对端 ACK 之前还能发送的数据量大小

从上面的定义可以看出来,真正的发送窗口的大小是rwnd和cwnd中的最小值。

如果接收窗口小于拥塞窗口,表示接收端的处理能力不足;如果接收窗口大于拥塞窗口,表示接收端的处理能力是ok的,但是网络拥塞。

拥塞处理

因为网络状况无法准确的去预测,所有需要对可能出现的网络拥塞做处理。

拥塞处理使用的几种算法:

  • 慢启动(Slow Start)
  • 拥塞避免(Congestion Avoidance)
  • 快速重传(Fast Retransmit)和快速恢复(Fast Recovery)

慢启动

在TCP连接建立之初,收发两端都不知道可用的带宽是多少,因此需要一个估算机制,然后还要根据网络中不断变化的条件而动态改变速度。

这里主要依靠上面的拥塞窗口来实现,系统的默认值都很小,随着请求的往返,如果在不丢包的情况下,这个拥塞窗口会“慢慢”地递增,这种机制就是“慢启动”。

拥塞避免

通过上图可以看出来,在连接往返的早起,拥塞窗口都是按照指数级别来增长的。但是如果一直按照指数级增长的话,那么拥塞控制就会失控。所以这里有个慢启动阈值(Slow Start Threshold,ssthresh),它是一个临界值。

TCP在通信开始的时候,并没有设置相应的慢启动阈值,而是在超时重发的时候,才会将阈值设置为此时拥塞窗口的一半。

  • 拥塞窗口小于阈值的时候,拥塞窗口按照指数级增长(慢启动),使窗口快速达到阈值,充分利用带宽
  • 拥塞窗口大于阈值的时候,拥塞窗口按线性增长(拥塞避免),即每次都增加一个固定的值

快速重传

当TCP连接中发生丢包,就会触发超时重传。完成一次超时重传,可能会消耗十几分钟,这个时间是难以接受的。

快速重传的策略是如果收到三个相同的ack,那么就认为是丢包了,立即重新发送对应的包,不用等到超时再重传。

快速恢复

TCP一旦出现丢包的情况,就会判断为网络拥塞,但是它不会把拥塞窗口重置为初始值,然后重复慢启动的过程。而是把当前的拥塞窗口减少到一半,并且以后都是线性增加窗口大小,这就是快速恢复。

从图中可以看出,超时重发和快速回复的拥塞窗口是不一样的。当超时重发的时候,拥塞窗口被充值未初始值,并且需要进行慢启动。

队首阻塞

TCP在不可靠的信道上实现了可靠的网络传输,必须要实现分组错误检测与纠正还有按序交付。

每个TCP分组都必须带有一个唯一的序列号被发出,而所有的分组必须按顺序传送到接收端。

如果中途有一个分组没有达到接收端,那么后续的分组必须保存在接收端的TCP缓冲区,等待丢失的分组重新发送到达接收端。

这一切都发生在TCP层,应用程序对TCP重发和缓冲区中排队的分组一无所知,必须要等待分组全部到达后由TCP处理完以后才能访问到对应的报文信息。

在此之前,应用程序只能在通过套接字读数据时感觉到数据的延迟交付。这就是TCP的队首阻塞。

http 3:QUIC

通过对TCP的介绍,我们知道TCP因为要保证连接的可靠性,而引入了三次握手、慢启动、拥塞处理等机制,在单个TCP连接承载页面所有的资源加载,能够享受多路复用带来的好处,但是在面对TCP层面的队首阻塞时,性能可能会有更大程度的下降,http 2.0对此无能为力。

Google开发的QUIC是快速UDP网络连接(Quick UDP Internet Connection)的缩写。它采纳了HTTP 2.0的优点,并且使用UDP代替了TCP,并加入了加解密、身份认证和一些其他特性,从根本上解决了TCP协议带来的问题。

为了实现可靠的数据传输,需要在UDP上实现TCP一系列的数据控制策略,比如拥塞控制、数据重传、安全握手等。

主要特点:

  • 多路复用,使用类似于http 2的流,不同的请求在不同的流上,就算丢包也不会互相影响,同时增加并发性
  • 改进的拥塞控制、可靠传输
  • 快速握手,因为是基于UDP的,所以可以实现 0-RTT 或者 1-RTT 来建立连接
  • 集成了 TLS 1.3 加密,可以减少TLS建立连接的时间

具体可以查看文档

https

HTTPS 是 HTTP + TLS 在功能上的组合,是一种安全的网络信息传输协议

数字签名

数字签名需要使用非对称加密算法来实现,比如RSA等。

签名过程

  • 发送者选择一个hash算法,比如SHA3-256,然后用这个hash算法对消息进行运算,生成消息摘要
  • 发送者用自己的私钥对消息摘要进行加密,这个密文就是签名消息
  • 发送者把消息原文和加密的摘要一起发送出去

在数字签名中,主要实现的是用户的认证,所以一般来说消息明文不用进行加密,这里也可以把使用的hash算法一起发送出去,或者双方提前约定好一个hash算法

认证过程

  • 接受者收到消息后,对消息进行处理,分离出消息明文和签名文件
  • 使用传递过来或者约定好的hash算法,这里使用的是SHA3-256,对消息明文进行hash处理
  • 使用发送者的公钥对签名文件已经解密,用解密后的内容和生成的hash值进行比较,如果相同则认证成功

CA证书

服务器向CA申请证书

  • 服务器实体希望发布一个https的网站(www.exmaple.com)
  • 服务器选择一种非对称加密算法,并生成一对密钥
  • 服务器生成一个CSR(Cerrificate Singing Request)证书,CSR是证书签名请求文件,其中包含的重要信息是网站的域名、服务器的公钥、营业执照,然后将CSR文件发送给CA机构申请证书
  • CA机构收到CSR文件后,核实申请者的身份
  • 一旦核实成功,CA就使用自己的私钥对CSR文件进行签名,然后将签名文件附在CSR文件后得到证书文件,证书文件除了包含申请者的信息,还包含CA机构的信息,比如CA签名用的算法,以及CA的机构名称
  • 最终CA机构将证书发送给服务器

浏览器如何校验证书

  • 浏览器向服务器发送连接请求 www.example.com
  • 服务器收到请求后,将证书和他自己的公钥一起发送给浏览器
  • 浏览器收到证书后,可以知道CA机构名称,和使用的加密算法。因为浏览器内置了CA机构的根证书,在根证书中包含了CA使用的加密算法的公钥,用CA的公钥对服务器证书中的签名进行解密,如果解密出来的内容和服务器发来的公钥一样,那么表明认证成功
  • 身份验证成功后,可以进行密钥协商。

TLS握手过程

  • Client Hello:客户端会发送一个Client Hello给服务器,包含一些信息:浏览的TLS版本、浏览器支持的加密组合列表、域名、seesonID
  • Server Hello:服务端在收到Client Hello后,会给浏览器发送一个Server Hello的包,其中包含一些信息:服务器选中的加密组合、服务器的证书、sessionID
  • 浏览器收到Server Hello后,对证书进行验证,如果验证通过,则会进行密钥协商,最后将密钥发送给服务器
  • 服务器收到浏览器的密钥后,后续的数据都会使用这个密钥进行加密

密钥协商和握手信息完整性校验

服务端选择的加密组合类似于 TLS_ECOHE_RSA_WITH_AES_128_GCM_SHA256,它表示协商密钥使用RSA进行传输,使用AES_128的算法进行数据加密,生成的协商密钥就是AES_128的密钥,hash算法使用SHA256来生成消息摘要

浏览器在本地生成AES_128的密钥后,使用服务器端的RSA公钥进行加密,将密文发送给服务器。服务器用自己的RSA私钥对密文解密后得到AES_128的密钥,后续的数据交换都使用这个密钥进行加密传输。

在密钥协商的过程中,服务器端还要对密钥进行完整性校验。

  • 浏览器将发送和接收的所有握手信息组合在一起,使用SHA256算出消息的摘要,并用协商密钥对这个摘要进行加密
  • 浏览器将消息铭文、加密后的摘要还有加密后的协商密钥(使用服务器的公钥进行加密)一起发送给服务器
  • 服务器收到这个握手消息后,先使用RSA私钥解密出协商密钥,再使用SHA256计算出消息摘要,然后使用协商密钥对摘要进行加密,最后用这个密文和收到的密文消息进行比较,如果相同,则证明了握手消息的完整性没有被篡改。

参考文献

《图解TCP/IP》

《web性能权威指南》

《图解http》

《HTTP/2基础教程》

一文读懂 HTTP/1HTTP/2HTTP/3

科普:QUIC协议原理分析