网络协议-浏览器中的HTTP请求

浏览器发起 HTTP 请求的一般步 :

  1. 检查本地缓存:检查是否存在本地缓存,如果存在直接使用。
  2. DNS 解析:将域名解析为 IP 地址。
  3. ARP 查询:获取目标 IP 地址对应的 MAC 地址(如果在同一局域网内)。
  4. TCP 三次握手:通过 TCP 三次握手建立连接。
  5. TLS 握手: 用于在浏览器和服务器之间协商加密参数和验证身份
  6. 基于 TCP 数据流 发送 HTTP 请求:通过建立的 TCP 连接将 HTTP 请求发送到服务器。
  7. 基于 TCP 数据流 接收 HTTP 响应:接收服务器返回的响应数据。
  8. 关闭连接:数据传输完成后,根据 HTTP 协议(如 HTTP/1.1 的 Connection: close 或 HTTP/2 的复用机制)关闭或保持连接。

HTTP 缓存

发起网络请求之前,查找缓存(上一次请求网站的 html、css、js、图片等文件资源的副本)。 如果命中缓存,会拦截请求,直接返回缓存数据,并直接结束请求,而不会再去源服务器重新下载。从而节省了网络请求的时间。

这样做的好处有:
1、减少网络带宽消耗
2、降低服务器压力
3、减少网络延迟,提升性能(获取资源的耗时更短了)

HTTP 缓存大小

并没有一个固定的上限,会因浏览器类型设备存储容量用户设置以及浏览器的自适应策略而有所不同。

一般来情况下:

  • 桌面浏览器 缓存大小可能在 50MB 到 200MB 之间
  • 移动浏览器 缓存大小可能在 20MB 到 50MB 之间。

HTTP 缓存 控制

HTTP 缓存分两类

1. 新鲜度(过期)
  • 浏览器缓存的有效期,缓存必须满足:
    • 含有完整的过期时间控制头信息,并在有效期内
    • 浏览器已经使用过这个副本,并且在会话中已经检查过新鲜度
2. 校验值(验证)
  • 服务器返回资源的时候,会在响应头信息中带上资源 实体标签 Entity Tag,用来作为浏览器再次请求过程的校验标识
  • 校验标识不匹配,说明资源已经被修改过或过期,浏览器需要重新请求资源。

HTTP 缓存机制分两种

强缓存

浏览器在请求资源时,会检查 响应请求头 的标识 Cache-Control 或者 Expires ,如果资源在缓存有效期内,直接使用本地副本,不再发起 HTTP 请求。

  • cache-control 相对过期时间(http1.1): cache-control: max-age=31536000, 优先级 高于Expires
  • Expires 绝对过期时间(http1.0): Expires: Wed, 21 Oct 2015 07:28:00 GMT, 是一个时间点, 是服务器时间,如果本地时间与服务器时间不一致,缓存将无法命中

cache-control 参数说明:

  • public 公共缓存,客户端和代理服务器都可缓存
  • private 私有缓存,只可以客户端缓存,不能在代理服务器处缓存
  • no-cache 不缓存响应内容,需要使用协商缓存来验证缓存是否可重用。一般用在index.html
  • no-store 不使用缓存,请求或响应都不可以缓存。

强缓存 > 协商缓存,如果强缓存生效,直接使用缓存,不会发起请求。

协商缓存
  • 浏览器 通过 响应头Last-ModifiedETag 发起请求
  • 服务器 通过 请求头If-Modified-SinceIf-None-Match与 服务器上 ETagLast-Modified进行对比,如果都没过期,返回304(资源未被修改,直接使用缓存),否则返200状态码。
  • 检查响应头的 Last-ModifiedETag 唯一标识符字段,这两个字段的值是服务器上资源的最后修改时间和 ETag 值。
  • If-Modified-SinceLast-Modified 对应,表示上一次请求时Last-Modified的值。
  • If-None-MatchETag 对应,表示上一次请求时ETag的唯一标识符值(通常是资源的哈希值)。
Last-ModifiedETag 一起使用
  • ETag主要是为了解决Last-Modified比较难解决的问题:
    1、Last-Modified 标注的时间仅到秒级, 如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度。
    2、如果 某些文件会被定期生成内容不变,但Last-Modified改变了,导致文件 无法使用缓存
    3、客户端与 服务器 时间不一致等情形。ETag是 服务器自动生成或开发者生成 对应资源在 服务器的 唯一标识符,更加精准控制缓存。
  • 两者可以一起使用,服务器优先验证ETag,一致时,才会继续比对Last-Mofifed,才决定是否要返回304。

304 状态码:表示 客户端请求的资源未被修改,允许客户端继续使用本地缓存,无需下载。

⚠️ 缓存验证机制:虽然 304 状态码可以 减少数据传输,但仍然 会发送 HTTP 请求,因此需要合理设置缓存策略(如 Cache-Control 和 Expires)。

Last-Modified / ETagCache-Control / Expires 一起使用时
  • 优先 检测 Cache-Control / Expires 本地缓存是否还在有效期内,在有效期内,直接使用本地缓存,阻止发送请求。
  • 如果过期,就走完整的协商缓存流程,返回304 或者 200。
  • 一般情况下,两者配合使用,
    • 因为即使设置缓存时间, 当用户点击“刷新”按钮时浏览器会忽略缓存继续向服务器发送请求,这时 Last-Modified/ETag将能够很好利用 304,从而减少响应开销。

不能缓存的请求

不是所有的请求都能被缓存:

  • post 请求 无法被缓存。
  • 需要根据cookie认证信息等 决定输入内容 的 动态请求 不能被缓存。
  • http 响应头 中不包含 Last-Modified/ETag,也**不包含Cache-Control/Expiress**的 请求 无法被缓存。
  • http 信息头 明确设置 Cache-Control:no-cacheCache-Control:max-age=0 ,浏览器 不缓存时。

浏览器缓存机制
HTTP | 强缓存与协商缓存

建立 TCP 连接
只有在以下情况下,浏览器才会建立 TCP 连接:

缓存中没有找到资源:即资源既不在内存缓存中,也不在磁盘缓存中。
缓存资源过期且服务器返回 200 OK:表示资源已更新,需要重新下载。

DNS 解析

域名是我们方便记忆的地址,而 IP 地址才是目标在网络中所被分配的节点。网络通讯大部分是基于TCP/IP的,而TCP/IP是基于IP地址的。 MAC 地址是对应目标网卡所在的固定地址。

DNS协议是应用层协议,通常该协议运行在UDP协议之上,使用的是53端口号。

DNS 解析会进行查询,过程分为以下几个步骤:

  • 递归查询 【递归查询一般发送一次请求就够】
    • 询问 浏览器 DNS 缓存
    • 询问 本地操作系统 DNS 缓存(即查找本地 host 文件)
    • 询问 本地 DNS 解析器 DNS 缓存
    • 询问 本地 DNS 服务器 【ISP(Internet Service Provider)互联网服务提供商(例如电信、移动)的 DNS 服务器】
      • 【 本地 DNS 服务器 没有会进入 迭代查询 阶段 】
      • 本地是个相对概念,指离用户最近的 DNS 服务器, 如 114、8.8.8.8 运营商搭建,提供更快的域名解析服务

dns解析

  • 迭代查询【迭代过程需要 ISP DNS 服务器 发送多次请求】
    • 从 本地 DNS 服务器 发起请求查询
      • 询问 根服务器、顶级域名服务器、权威域名服务器

dns解析

  • 根域名:www.hy.com. 中最后一个单个句点(.)或句点用于末尾的名称 [ 默认省略, 由ICANN 规定顶级域名 ]
  • 顶级域名 TLD:.com
  • 二层域名:hy.com
  • 子域:www.hy.com
  • 主机名:h1.www.hy.com

以用户输入的三级域名网址为例,本地DNS 查询 解析过程 如下:

  • 根域名服务器 没有 主机域名记录,返回 根域名服务器的 IP 地址
  • 本地域名服务器向 cn 域名服务器查询,也没有相关记录,返回 二级域名服务器的 IP 地址
  • 本地域名服务器再 二级域名服务器查询,也没有相关记录,但是知道 三级域名服务器可以解析这个域名,返回 三级域名服务器的 IP 地址
  • 本地域名服务器向 三级域名服务器查询,发现有主机域名记录,返回对应的 IP 地址
  • 最后 本地域名服务器将 对应的 IP 地址返回给客户端,整个解析过程完成

为什么DNS可以实现负载均衡?
DNS可以在 冗余的服务器上实现负载均衡,一般的大型网站 使用多台服务器提供服务,因此一个域名 可能会对应 多个服务器地址。这样可以 将用户的请求 均衡的分配 到各个不同的服务器上,这样来实现负载均衡。

建立 TCP 连接

得到 IP 后, 首先,建立 TCP 连接,即三次握手过程:

  1. 客户端发送标有 SYN = 1,seq = x 的数据包,表示 将要发送请求(x 为随机数)。
  2. 服务器发送标有 SYN = 1,ACK=1, ack = x + 1,seq = y 的数据包,表示 已经收到通知,告知客户端发送请求(y 为随机数)。
  3. 客户端发送标有 ACK = 1, ack = y + 1,seq = x + 1 的数据包,表示 确认收到服务器的响应

SYN 表示 发送请求,ACK 表示 确认收到,seq 表示 序列号,ack 表示 确认号。
seq序列号初始值是随机的,避免重放攻击。

tcp 三次握手

三次握手的设计原因

  1. 为了防止已过期的连接请求报文突然又传送到服务器,因而产生错误。
    - 例如:客户端发送的第一个连接请求报文段因网络延迟被延迟到达服务器,那么进行三次握手可以防止服务器错误地打开一个已经失效的连接。
  2. 三次握手才能让双方均确认自己和对方的发送和接收能力都正常
    - 第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力客户端的发送能力正常;
    - 第二次握手:服务器发送确认报文段,客户端可以确认自己和对方的发送、接收能力正常
    - 第三次握手:客户端发送确认报文段,服务器可以确认自己的发送能力正常
  3. 告知对方自己的初始序号值,并确认收到对方的初始序号值
    - 依靠 TCP 报文段中维护了 序号字段seq 和 确认序号字段 ack 使得双方都能得到数据确认, 这也是 TCP 能够实现可靠的数据传输 的原因之一。

方便记忆场景:

  1. 客户端: SYN:我给你的序号是5
  2. 服务端: ACK:我收到了你的序号是5; SYN:我也给你一个序号,是10
  3. 客户端: ACK:你的序号是10。现在,我要开始发内容了。

详解 TCP 三次握手、四次挥手,附带精美图解和超高频面试题

TLS 握手

在 TCP 连接建立成功后,浏览器和服务器通过 TLS 握手协商加密参数并验证身份
TLS 握手完成后,所有应用层数据(如 HTTP 请求和响应)都会通过加密通道传输

TLS 握手的主要目的是: 握手的目的是确保通信的安全性,防止中间人攻击

  • 验证服务器(和可选的客户端)身份。
  • 协商加密算法。
  • 生成共享的会话密钥。
  • 确保通信的安全性。

TLS 握手的过程

tls.png

以 TLS 1.2 为例:

  1. 浏览器发送 Client Hello 消息。
  • 客户端(浏览器)向服务器发送一个 Client Hello 消息,内容包括:
    • 客户端支持的 TLS 版本。
    • 客户端生成的随机数(Client Random)。
    • 支持的加密算法套件(Cipher Suites)。
    • 支持的压缩算法(可选)。
    • 扩展信息(如支持的椭圆曲线、签名算法等)。
  1. 服务器发送 Server Hello 消息。
  • 服务器收到 Client Hello 后,选择一个加密算法套件,并生成自己的随机数(Server Random),然后发送 Server Hello 消息,内容包括:
    • 选定的 TLS 版本。
    • 服务器随机数。
    • 选定的加密算法套件。
    • 会话 ID(可选)。
  1. 证书交换
  • 服务器发送自己的证书(或证书链),客户端通过证书颁发机构(CA)验证服务器的身份。
  • 如果服务器需要客户端证书,还会发送 Certificate Request
  1. 密钥协商
  • 客户端生成一个预主密钥(Pre-Master Secret),并使用服务器证书中的公钥加密后发送给服务器。
  • 客户端和服务器分别使用客户端随机数、服务器随机数和预主密钥,通过相同的算法生成主密钥(Master Secret)。
  • 双方使用主密钥生成用于加密通信的会话密钥。
  1. Change Cipher SpecFinished
  • 客户端和服务器分别发送 Change Cipher Spec 消息,通知对方 后续通信将 使用协商好的加密算法和密钥
  • 双方发送 Finished 消息,验证握手过程的完整性和正确性。
  • 如果验证成功,握手完成,开始加密通信。
  1. TLS 1.3 的改进

TLS 1.3 对握手过程进行了优化,主要改进包括: 减少了握手时间和往返次数,提高了性能和安全性

  • 减少握手往返次数:TLS 1.3 将握手过程减少到 1-RTT(一次往返),甚至在某些情况下可以实现 0-RTT(零往返)。
  • 更安全的密钥交换算法:TLS 1.3 引入了更安全的密钥交换算法(如 ECDHE),并废弃了不安全的算法(如 RSA 密钥交换)。
  • 部分握手信息加密:TLS 1.3 对握手过程中的部分信息进行了加密,增强了安全性。

https 与 http 核心区别:
HTTP 基于 TCP 协议,信息是明文传输;
HTTPS 在 TCP 之上引入了 TLS/SSL 协议, 信息是加密传输。

HTTP 请求报文

基于 TCP 文件流 发送的资源:

请求行
请求头
请求体(只有post请求有,get请求没有)

HTTP 响应报文

响应行:版本协议、状态码
响应头
响应体

断开连接

四次挥手详细过程如下:

  • 客户端发送 关闭连接 FIN =1, seq = x 报文段,并停止发送数据。(x等于 之前发送的所有数据的 最后一个字节的序号加一)
    • 客户端状态 变更为 FIN-WAIT-1状态,等待来自 服务器的确认报文
  • 服务器 收到 FIN 报文后,发回 确认报文:ACK = 1, ack = x + 1, seq = y,并带上自己的序号y
    • 服务器 变更为 CLOSE-WAIT状态(服务器还可以发送数据)。服务器还会 通知上层的应用程序 对方已经释放连接,没有数据要发送了;TCP 处于半关闭状态
    • 客户端 在收到服务器的 ACK 报文段后, 随即进入 FIN-WAIT-2 状态(还能收到来自服务器的数据
  • 服务器发 送完所有数据后,会向客户端发送 FIN =1 ACK =1 ack=x+1 seq=z 报文段,
    • 服务器 变更为 LAST-ACK 状态,等待来自客户端的确认报文段
  • 客户端收到 服务器的 FIN 报文段后,发送 ACK = 1, ack = z + 1, seq = x + 1
    • 服务器收到 客户端的 ACK 报文 后 变更为 CLOSED 状态,因为 没有等待时间,服务器比客户端更早进入 CLOSED 状态
    • 客户端 变更 TIME-WAIT 状态,等待 2MSL(两倍的报文段最大存活时间,常用值有30秒、1分钟和2分钟)。
    • 最后,无特殊情况,客户端 进入 CLOSED 状态
      tcp 四次挥手

为什么 TCP 关闭连接 为什么 四次而不是三次?

  • 服务器 收到客户端的 FIN 报文段后,可能还有数据要传输不能马上关闭连接, 但是会做出应答,返回 ACK 报文段,在数据发送完后,服务器会向客户 单发送 FIN 报文,表示 数据发送完毕,请求关闭连接;
  • 然后客户端再做出应答,因此一共需要四次挥手。
  • 根本原因 :TCP 连接是 全双工 (即数据在两个方向上能同时传递,同时双向传输数据), 因此每个方向必须单独地进行关闭

客户端为什么需要在 TIME-WAIT 状态 等待 2MSL 时间才能进入 CLOSED 状态

  • 网络并不总是可靠的,如果 客户端发送的 ACK 报文段丢失,服务器在接收不到 ACK 的情况下,会一直重发 FIN 报文段.
  • 为此 客户端为了确保服务器收到了 ACK,会设置一个定时器,并在 TIME-WAIT 状态等待 2MSL 的时间
  • 收到 服务器的 FIN 报文段,此时 客户端会 重置计时器并再次等待 2MSL 的时间
  • 没收到 FIN 报文,说明成功收到了 ACK 报文,客户端就可以进入 CLOSED 状态了。