浏览器发起 HTTP 请求的一般步 :
- 检查本地缓存:检查是否存在本地缓存,如果存在直接使用。
- DNS 解析:将域名解析为 IP 地址。
- ARP 查询:获取目标 IP 地址对应的 MAC 地址(如果在同一局域网内)。
- TCP 三次握手:通过 TCP 三次握手建立连接。
- TLS 握手: 用于在浏览器和服务器之间协商加密参数和验证身份
- 基于 TCP 数据流 发送 HTTP 请求:通过建立的 TCP 连接将 HTTP 请求发送到服务器。
- 基于 TCP 数据流 接收 HTTP 响应:接收服务器返回的响应数据。
- 关闭连接:数据传输完成后,根据 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.htmlno-store
不使用缓存,请求或响应都不可以缓存。
强缓存
>协商缓存
,如果强缓存生效,直接使用缓存,不会发起请求。
协商缓存
- 浏览器 通过 响应头 的 Last-Modified 和 ETag 发起请求
- 服务器 通过 请求头 的 If-Modified-Since 和 If-None-Match与 服务器上
ETag
和Last-Modified
进行对比,如果都没过期,返回304(资源未被修改,直接使用缓存),否则返200状态码。 - 检查响应头的
Last-Modified
和ETag
唯一标识符字段,这两个字段的值是服务器上资源的最后修改时间和 ETag 值。 If-Modified-Since
与Last-Modified
对应,表示上一次请求时Last-Modified
的值。If-None-Match
与ETag
对应,表示上一次请求时ETag
的唯一标识符值(通常是资源的哈希值)。
Last-Modified
与 ETag
一起使用
ETag
主要是为了解决Last-Modified
比较难解决的问题:
1、Last-Modified
标注的时间仅到秒级, 如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度。
2、如果 某些文件会被定期生成内容不变,但Last-Modified
改变了,导致文件 无法使用缓存。
3、客户端与 服务器 时间不一致等情形。ETag是 服务器自动生成或开发者生成 对应资源在 服务器的 唯一标识符,更加精准控制缓存。- 两者可以一起使用,服务器优先验证
ETag
,一致时,才会继续比对Last-Mofifed
,才决定是否要返回304。
304 状态码:表示 客户端请求的资源未被修改,允许客户端继续使用本地缓存,无需下载。
⚠️ 缓存验证机制:虽然 304 状态码可以 减少数据传输,但仍然 会发送 HTTP 请求,因此需要合理设置缓存策略(如 Cache-Control 和 Expires)。
Last-Modified / ETag
与 Cache-Control / Expires
一起使用时
- 优先 检测
Cache-Control / Expires
本地缓存是否还在有效期内,在有效期内,直接使用本地缓存,阻止发送请求。 - 如果过期,就走完整的协商缓存流程,返回304 或者 200。
- 一般情况下,两者配合使用,
- 因为即使设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时
Last-Modified/ETag
将能够很好利用 304,从而减少响应开销。
- 因为即使设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时
不能缓存的请求
不是所有的请求都能被缓存:
post 请求
无法被缓存。- 需要根据
cookie
、认证信息
等 决定输入内容 的 动态请求 不能被缓存。 - http 响应头 中不包含
Last-Modified/ETag
,也**不包含Cache-Control/Expiress
**的 请求 无法被缓存。 - http 信息头 明确设置
Cache-Control:no-cache
或Cache-Control:max-age=0
,浏览器 不缓存时。
建立 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 运营商搭建,提供更快的域名解析服务
- 迭代查询【迭代过程需要 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 连接,即三次握手过程:
- 客户端发送标有
SYN = 1,seq = x
的数据包,表示 将要发送请求(x 为随机数)。 - 服务器发送标有
SYN = 1,ACK=1, ack = x + 1,seq = y
的数据包,表示 已经收到通知,告知客户端发送请求(y 为随机数)。 - 客户端发送标有
ACK = 1, ack = y + 1,seq = x + 1
的数据包,表示 确认收到服务器的响应。
SYN 表示 发送请求,ACK 表示 确认收到,seq 表示 序列号,ack 表示 确认号。
seq序列号初始值是随机的,避免重放攻击。
三次握手的设计原因
- 为了防止已过期的连接请求报文突然又传送到服务器,因而产生错误。
- 例如:客户端发送的第一个连接请求报文段因网络延迟被延迟到达服务器,那么进行三次握手可以防止服务器错误地打开一个已经失效的连接。 - 三次握手才能让双方均确认自己和对方的发送和接收能力都正常
- 第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和客户端的发送能力正常;
- 第二次握手:服务器发送确认报文段,客户端可以确认自己和对方的发送、接收能力正常;
- 第三次握手:客户端发送确认报文段,服务器可以确认自己的发送能力正常。 - 告知对方自己的初始序号值,并确认收到对方的初始序号值。
- 依靠 TCP 报文段中维护了 序号字段seq
和 确认序号字段ack
使得双方都能得到数据确认, 这也是 TCP 能够实现可靠的数据传输 的原因之一。
方便记忆场景:
- 客户端: SYN:我给你的序号是5
- 服务端: ACK:我收到了你的序号是5; SYN:我也给你一个序号,是10
- 客户端: ACK:你的序号是10。现在,我要开始发内容了。
详解 TCP 三次握手、四次挥手,附带精美图解和超高频面试题
TLS 握手
在 TCP 连接建立成功后,浏览器和服务器通过 TLS 握手协商加密参数并验证身份
TLS 握手完成后,所有应用层数据(如 HTTP 请求和响应)都会通过加密通道传输
TLS 握手的主要目的是: 握手的目的是确保通信的安全性,防止中间人攻击
- 验证服务器(和可选的客户端)身份。
- 协商加密算法。
- 生成共享的会话密钥。
- 确保通信的安全性。
TLS 握手的过程
以 TLS 1.2 为例:
- 浏览器发送 Client Hello 消息。
- 客户端(浏览器)向服务器发送一个 Client Hello 消息,内容包括:
- 客户端支持的 TLS 版本。
- 客户端生成的随机数(Client Random)。
- 支持的加密算法套件(Cipher Suites)。
- 支持的压缩算法(可选)。
- 扩展信息(如支持的椭圆曲线、签名算法等)。
- 服务器发送 Server Hello 消息。
- 服务器收到 Client Hello 后,选择一个加密算法套件,并生成自己的随机数(Server Random),然后发送 Server Hello 消息,内容包括:
- 选定的 TLS 版本。
- 服务器随机数。
- 选定的加密算法套件。
- 会话 ID(可选)。
- 证书交换
- 服务器发送自己的证书(或证书链),客户端通过证书颁发机构(CA)验证服务器的身份。
- 如果服务器需要客户端证书,还会发送
Certificate Request
。
- 密钥协商
- 客户端生成一个预主密钥(Pre-Master Secret),并使用服务器证书中的公钥加密后发送给服务器。
- 客户端和服务器分别使用客户端随机数、服务器随机数和预主密钥,通过相同的算法生成主密钥(Master Secret)。
- 双方使用主密钥生成用于加密通信的会话密钥。
Change Cipher Spec
和Finished
- 客户端和服务器分别发送
Change Cipher Spec
消息,通知对方 后续通信将 使用协商好的加密算法和密钥。 - 双方发送 Finished 消息,验证握手过程的完整性和正确性。
- 如果验证成功,握手完成,开始加密通信。
- 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 关闭连接 为什么 四次而不是三次?
- 服务器 收到客户端的
FIN 报文段
后,可能还有数据要传输
,不能马上关闭连接
, 但是会做出应答,返回 ACK 报文段,在数据发送完后,服务器会向客户 单发送FIN 报文
,表示 数据发送完毕,请求关闭连接; - 然后客户端再做出应答,因此一共需要四次挥手。
- 根本原因 :TCP 连接是 全双工 (即数据在两个方向上能同时传递,同时双向传输数据), 因此每个方向必须单独地进行关闭
客户端为什么需要在 TIME-WAIT 状态
等待 2MSL 时间才能进入 CLOSED 状态
?
网络并不总是可靠的
,如果客户端发送的 ACK 报文段
丢失,服务器在接收不到 ACK 的情况下,会一直重发FIN 报文段
.- 为此 客户端为了确保服务器收到了 ACK,会
设置一个定时器
,并在TIME-WAIT 状态等待 2MSL 的时间
, - 收到
服务器的 FIN 报文段
,此时 客户端会重置计时器并再次等待 2MSL 的时间
, - 没收到
FIN 报文
,说明成功收到了ACK 报文
,客户端就可以进入CLOSED 状态
了。