阅读 233

HTTP、HTTPS和HTTP2的简单了解

HTTP的概述

HTTP 超文本传输协议: 其位于 TCP/IP 体系结构中的应用层协议,是万维网数据通信的基础。


<协议>://<域名>:<端口号>/<路径> 复制代码

一般一个地址的URL构成形式就如http://fanyi.baidu.com/translate,现在常用的协议就是HTTP,如无设置端口号,则默认端口号为80。

5qlmJx.jpg

HTTP/1.1

目前被使用被广泛的版本,一般没有特殊标明的都指 HTTP/1.1


HTTP 连接的建立过程

  1. 通过 域名系统 (Domain Name System 简称DNS) 将域名转换成 IP 地址。

  2. 通过三次握手建立 TCP/IP 的连接

  3. 发起HTTP请求

  4. 目标服务器获取请求并且处理

  5. 目标服务器往浏览器发送HTTP相应

  6. 浏览器解析并且渲染页面

TCP的三次握手

TCP(Transmission Control Protocol 传输控制协议)是属于网络分层中的传输层,提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。


首先要了解这三次握手就要先了解一些TCP报文段和标志位

TCP报文结构

5qWmN9.png

  1. 序号 Seq: 占32位,用于标识TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

  2. 确认号:Ack,占32位,只有 ACK标志位为1时,确认字段才有效, Ack = Seq + 1

  3. 标志位:一共有6个

  • URG:紧急指针有效

  • ACK:确认序号有效

  • PSH:接收方应该尽快将这个报文交给应用层

  • RST:重置连接

  • SYN:发起一个新连接

  • FIN:释放一个连接

需要注意的是:

  • 不要将 确认号Ack 和标志位的 ACK确认序号 两者搞混

  • 确认方的Ack = 发起方的seq + 1

在熟悉完TCP的一些简单报文和标志位之后,再来看TCP的三次握手。

TCP的三次握手,即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以来确认连接的建立。

  1. 客户端向服务端发送一个TCP报文,首部内的SYN标志位置为1,并且随机一个初始的序号(一般为0)放到报文序号字段中。即Seq = 0,SYN = 1(SYN = 1 时,不能携带数据,并且要消耗一个序号)Client进入SYN_SENT状态,等待Server确认。

  2. 服务器接收到报文后,并且为该TCP报文连接分配缓存和变量。然后向客户端发送允许连接的ACK报文段。该报文段首部包括四个信息:即SYN = 1;ACK = 1;确认号字段(Ack)= 客户端序号(Seq) + 1;随机选择自己的序号(一般为0)Seq = 0。Server进入SYN_RCVD状态。

  3. 收到服务器的 TCP 响应报文段后,客户端也要为该 TCP 连接分配缓存和变量,并向服务器发送一个 ACK 报文段。该报文段的内容为:确认号字段(Ack) = 服务端的序号 + 1,用来确认对服务器运行连接的报文段进行相应,因为连接已经建立,所以SYN = 0,最后一个阶段报文段可以携带客户到服务端数据。并且以后的每一个报文段,SYN 都置为 0。

5qXl0s.md.png

TCP的四次挥手

所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。

  1. Client发送一个FIN的标志,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

  2. Server收到FIN后,发送一个ACK给Client,确认号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

  3. Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

  4. Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

TCP 为什么是四次挥手,而不是三次?

这是因为服务端在 LISTEN 状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

可以理解为:

  1. A向B发送FIN报文时,仅仅能代表A不再发送报文,但是仍可以接受报文。

  2. B或者还有数据发送,因此需要先发送 ACK 报文给 A,确认号(Ack = seq + 1)。告知 A “我知道你想断开连接的请求了”。这样 A 便不会因为没有收到应答而继续发送断开连接的请求(即 FIN 报文)

  3. B再处理完数据后,就会向A发送一个FIN报文,然后进入 LAST_ACK 阶段(超时等待)。

  4. A 向 B 发送 ACK 报文,双方都断开连接。

HTTP报文格式

HTTP报文是由请求行、首部和实体主体组成,他们之间由CRLF(回车换行符)隔开

注意:实体包括首部(也成为实体首部)和实体主体

5LPsN4.md.jpg

开始行(也可称为请求行)和首部是由 ASCII 文本组成的,实体主体是可选的,可以为空也可以是任意二进制数据。

请求报文和响应报文的格式基本相同。

请求报文格式:

<method> <request-URL> <version> <headers> <entity-body> 复制代码

响应报文

<version> <status> <reason-phrase> <headers> <entity-body> 复制代码

一个 HTTP 请求示例:

POST /tenant/material-center/hd/getQuestion HTTP/1.1 Host: api.testing.jniu.com Connection: keep-alive Content-Length: 212 Accept: application/json, text/plain, X-TraceId: 93704a4d259322f0b57510fbe05ac7ad sessionId: 36d1cc12956f5cdf77efd38f10657545 User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Content-Type: application/json; charset=UTF-8 Origin: http://localhost:3000 Sec-Fetch-Site: cross-site Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: http://localhost:3000/jniu-ng-agent/testTool/toC/detail Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 复制代码

一个响应示例

HTTP/1.1 200 OK Server: openresty Date: Thu, 28 Oct 2021 08:39:18 GMT Content-Type: application/json;charset=UTF-8 Content-Length: 552 Connection: keep-alive Access-Control-Allow-Origin: * Content-Encoding: gzip Vary: Accept-Encoding Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Frame-Options: Deny 复制代码

方法:

方法描述
GET从服务器获取一份文档
HEAD只从服务器获取文档的头部
POST向服务器发送需要处理的数据
PUT将请求的数据部分存储在服务器上
TRACE对可能经过代理服务器传送到服务器上去的报文进行追踪
OPTIONS决定可以在服务器上执行哪些方法
DELETE从服务器上删除一份文档

状态码

整体范围已定义范围分类
100~199100~101信息提示
200~299200~206成功
300~399300~305重定向
400~499400~415客户端错误
500~599500~505服务器错误

首部

首部和方法共同决定了客户端和服务端能做什么事。

首部分类:

  1. 通用首部,可以出现在请求或响应报文中。

  2. 请求头部,提供更多的请求相关的信息。

  3. 响应头部,提供更多的响应相关的信息。

  4. 实体首部,描述主体的长度和内容,或者资源本身。

  5. 扩展首部,规范字没有定义的新首部。

通用头部

有些首部提供了与报文相关的最基本信息,它们被称为通用首部。以下是一些常见的通用首部:

5Xex9f.png

请求头部

请求首部是只在请求报文中有意义的首部,用于说明请求的详情。以下是一些常见的请求首部:

5XmLMF.png

响应头部

响应首部让服务器为客户端提供了一些额外的信息。

5XKaKU.png

实体头部

实体首部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。

5XKLM8.png

性能优化

1. 减少HTTP请求

每发起一个 HTTP 请求,都得经历三次握手建立 TCP 连接,如果连接只用来交换少量数据,这个过程就会严重降低 HTTP 性能。所以我们可以将多个小文件合成一个大文件,从而减少 HTTP 请求次数。 其实由于持久连接(重用 TCP 连接,以消除连接及关闭时延;HTTP/1.1 默认开启持久连接)的存在,每个新请求不一定都需要建立一个新的 TCP 连接。但是,浏览器处理完一个 HTTP 请求才能发起下一个,所以在 TCP 连接数没达到浏览器规定的上限时,还是会建立新的 TCP 连接。从这点来看,减少 HTTP 请求仍然是有必要的。

2. 静态资源使用 CDN

内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

3.善用缓存

为了避免用户每次访问网站都得请求文件,我们可以通过添加 Expires 头来控制这一行为。Expires 设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。

不过这样会产生一个问题,当文件更新了怎么办?怎么通知浏览器重新请求文件?

可以通过更新页面中引用的资源链接地址,让浏览器主动放弃缓存,加载新资源。

具体做法是把资源地址 URL 的修改与文件内容关联起来,也就是说,只有文件内容变化,才会导致相应 URL 的变更,从而实现文件级别的精确缓存控制。什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。

4.压缩文件

压缩文件可以减少文件下载时间,让用户体验性更好。

gzip 是目前最流行和最有效的压缩方法。可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能。当然,服务器也得支持这一功能。 举个例子,我用 Vue 开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减少了将近 60%。

5.通过 max-age 和 no-cache 实现文件精确缓存

通用消息头部 Cache-Control 其中有两个选项:

  1. max-age: 设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。在这个时间前,浏览器读取文件不会发出新请求,而是直接使用缓存。

  2. no-cache: 指定 no-cache 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。

我们可以将那些长期不变的静态资源设置一个非常长的缓存时间,例如设置成缓存一年。 然后将 index.html 文件设置成 no-cache 这样每次访问网站时,浏览器都会询问 index.html 是否有更新,如果没有,就使用旧的 index.html文件。如果有更新,就读取新的index.html文件。当加载新的index.html时,也会去加载里面新的 URL 资源。 例如 index.html 原来引用了 a.jsb.js,现在更新了变成 a.jsc.js。那就只会加载 c.js 文件。

HTTPS

HTTPS 是最流行的 HTTP 安全形式,由网景公司首创,所有主要的浏览器和服务器都支持此协议。 使用 HTTPS 时,所有的 HTTP 请求和响应数据在发送之前,都要进行加密。加密可以使用 SSL 或 TLS。

SSL/TLS 协议作用在 HTTP 协议之下,对于上层应用来说,原来的发送/接收数据流程不变,这就很好地兼容了老的 HTTP 协议。由于 SSL/TLS 差别不大,下面统一使用 SSL。

HTTP2详解,暂时还未理解,暂时先不写。

HTTP/2

HTTP/2 是 HTTP/1.x 的扩展,而非替代。所以 HTTP 的语义不变,提供的功能不变,HTTP 方法、状态码、URL 和首部字段等这些核心概念也不变。之所以要递增一个大版本到 2.0,主要是因为它改变了客户端与服务器之间交换数据的方式。HTTP 2.0 增加了新的二进制分帧数据层,而这一层并不兼容之前的 HTTP 1.x 服务器及客户端——是谓 2.0。

HTTP/2 连接建立过程

现在的主流浏览器 HTTP/2 的实现都是基于 SSL/TLS 的,也就是说使用 HTTP/2 的网站都是 HTTPS 协议的,所以本文只讨论基于 SSL/TLS 的 HTTP/2 连接建立过程。 基于 SSL/TLS 的 HTTP/2 连接建立过程和 HTTPS 差不多。在 SSL/TLS 握手协商过程中,客户端在 ClientHello 消息中设置 ALPN(应用层协议协商)扩展来表明期望使用 HTTP/2 协议,服务器用同样的方式回复。通过这种方式,HTTP/2 在 SSL/TLS 握手协商过程中就建立起来了。

现在的主流浏览器 HTTP/2 的实现都是基于 SSL/TLS 的,也就是说使用 HTTP/2 的网站都是 HTTPS 协议的,所以本文只讨论基于 SSL/TLS 的 HTTP/2 连接建立过程。 基于 SSL/TLS 的 HTTP/2 连接建立过程和 HTTPS 差不多。在 SSL/TLS 握手协商过程中,客户端在 ClientHello 消息中设置 ALPN(应用层协议协商)扩展来表明期望使用 HTTP/2 协议,服务器用同样的方式回复。通过这种方式,HTTP/2 在 SSL/TLS 握手协商过程中就建立起来了。

HTTP/1.1 的问题

1.队头阻塞

在 HTTP 请求应答过程中,如果出现了某种情况,导致响应一直未能完成,那后面所有的请求就会一直阻塞着,这种情况叫队头阻塞。

2.低效TCP利用

由于 TCP 慢启动机制,导致每个 TCP 连接在一开始的时候传输速率都不高,在处理多个请求后,才会慢慢达到“合适”的速率。对于请求数据量很小的 HTTP 请求来说,这种情况就是种灾难。

3. 臃肿的消息首部

HTTP/1.1 的首部无法压缩,再加上 cookie 的存在,经常会出现首部大小比请求数据大小还大的情况

4. 受限的优先级设置

HTTP/1.1 无法为重要的资源指定优先级,每个 HTTP 请求都是一视同仁。

二进制分帧层

HTTP/2 是基于帧的协议。采用分帧是为了将重要信息封装起来,让协议的解析方可以轻松阅读、解析并还原信息。

而 HTTP/1.1 是以文本分隔的。解析 HTTP/1.1 不需要什么高科技,但往往速度慢且容易出错。你需要不断地读入字节,直到遇到分隔符 CRLF 为止,同时还要考虑不守规矩的客户端,它只会发送 LF。

解析 HTTP/1.1 的请求或响应还会遇到以下问题:

  1. 一次只能处理一个请求或响应,完成之前不能停止解析。

  2. 无法预判解析需要多少内存。

HTTP/2 有了帧,处理协议的程序就能预先知道会收到什么,并且 HTTP/2 有表示帧长度的字段。

帧结构
 +-----------------------------------------------+  |                 Length (24)                   |  +---------------+---------------+---------------+  |   Type (8)    |   Flags (8)   |  +-+-------------+---------------+-------------------------------+  |R|                 Stream Identifier (31)                      |  +=+=============================================================+  |                   Frame Payload (0...)                      ...  +---------------------------------------------------------------+ 复制代码

名称长度描述
Length3字节表示帧负载的长度,取值范围为 (2 的 14 次方)至 (2 的 24 次方 - 1)。(2 的 14 次方) 16384 字节是默认的最大帧大小,如果需要更大的帧,必须在 SETTINGS 帧中设置
Type1字节当前帧类型(见下表)
Flags1字节具体帧类型的标识
R1字节保留位,不要设置,否则可能会带来严重的后果
Stream Identifier31字节每个流的唯一 ID
Frame Payload长度不变真实的帧内容,长度是在 Length 字段中设置的

由于 HTTP/2 是分帧的,请求和响应都可以多路复用,有助于解决类似类似队头阻塞的问题。

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

多路复用

在 HTTP/1.1 中,如果客户端想发送多个并行的请求,那么必须使用多个 TCP 连接。

而 HTTP/2 的二进制分帧层突破了这一限制,所有的请求和响应都在同一个 TCP 连接上发送:客户端和服务器把 HTTP 消息分解成多个帧,然后乱序发送,最后在另一端再根据流 ID 重新组合起来。

这个机制为 HTTP 带来了巨大的性能提升,因为:

  • 可以并行交错地发送请求,请求之间互不影响;

  • 可以并行交错地发送响应,响应之间互不干扰;

  • 只使用一个连接即可并行发送多个请求和响应;

  • 消除不必要的延迟,从而减少页面加载的时间;

  • 不必再为绕过 HTTP 1.x 限制而多做很多工作;

HTTP/2 规范对流的定义是:HTTP/2 连接上独立的、双向的帧序列交换。如果客户端想要发出请求,它会开启一个新流,然后服务器在这个流上回复。 由于有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流 ID 用来标识帧所属的流。

客户端到服务器的 HTTP/2 连接建立后,通过发送 HEADERS 帧来启动新的流。如果首部需要跨多个帧,可能还会发送 CONTINUATION 帧。该 HEADERS 帧可能来自请求或响应。 后续流启动的时候,会发送一个带有递增流 ID 的新 HEADERS 帧。

服务器推送

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

首部压缩

HTTP/1.1 存在的一个问题就是臃肿的首部,HTTP/2 对这一问题进行了改进,可以对首部进行压缩。 在一个 Web 页面中,一般都会包含大量的请求,而其中有很多请求的首部往往有很多重复的部分。

性能优化

使用 HTTP/2 代替 HTTP/1.1,本身就是一种巨大的性能提升。 这小节要聊的是在 HTTP/1.1 中的某些优化手段,在 HTTP/2 中是不必要的,可以取消的。

  • 取消合并资源:在 HTTP/1.1 中要把多个小资源合并成一个大资源,从而减少请求。而在 HTTP/2 就不需要了,因为 HTTP/2 所有的请求都可以在一个 TCP 连接发送。

  • 取消域名拆分: 取消域名拆分的理由同上,再多的 HTTP 请求都可以在一个 TCP 连接上发送,所以不需要采取多个域名来突破浏览器 TCP 连接数限制这一规则了。


作者:轻云淡雾
链接:https://juejin.cn/post/7025800232600354829


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐