前言
计算机网络协议的重要性不言而喻,下面一起梳理一下重点知识。
一、URL和URI
1、谈一谈你对URL理解
统一资源定位符的简称,Uniform Resource Locator,常常被称为网址,是因特网上标准的资源地址。
组成
通用的格式:scheme://host[:port]/path/…/?query#anchor
| 名称 | 功能 |
|---|---|
| scheme | 访问服务器以获取资源时要使用哪种协议,比如:http,https 和 FTP 等 |
| host | HTTP 服务器的 IP 地址或者域名 |
| port | HTTP 服务器的默认端口是 80,HTTPS默认端口是443,这种情况下端口号可以省略,如果使用了别的端口,必须指明。不同的端口,你可以认为是不同的应用程序。 |
| path | 访问资源的路径 |
| query-string | 发给 http 服务器的数据 |
| anchor | 锚点 |
举个例子
https://www.baidu.com/s?tn=baidu&bar=&wd=TianTian
这个URL中,https就是协议,www.baidu.com就是域名,默认端口是443,/s就是请求的path,tn=baidu&bar=&wd=TianTian这个就是query
URL 编码
- URL 只能使用 ASCII 字符集来通过因特网进行发送。
- 由于 URL 常常会包含 ASCII 集合之外的字符,URL 必须转换为有效的 ASCII 格式。
- URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 字符。
- URL 不能包含空格。URL 编码通常使用 + 来替换空格。
举个例子
天天转换为有效的ASCII格式就是%CC%EC%CC%EC
2、简述URL和URI的区别
URI(统一资源标识符)和URL(统一资源定位符)的核心区别在于:URI是用于唯一标识资源的抽象概念,而URL是URI的一种具体实现,不仅标识资源还提供了访问该资源的位置信息。
简单来说,每个URL都是URI,但并非所有URI都是URL。
3、encodeURI和encodeURIComponent区别
核心区别
-
编码范围:
encodeURI不会对 URI 的合法分隔符进行编码,例如;,/,?,:,@,&,=,+,$,,,#等。它仅对空格等非安全字符进行转义(如空格转为%20),以确保整个 URL 可被浏览器直接解析。encodeURIComponent会对所有非字母数字字符(包括上述 URI 分隔符)进行编码,例如:转为%3A,&转为%26。这使得它适合处理参数值,避免特殊字符干扰 URL 结构。
-
设计目的:
encodeURI的目标是生成一个可直接用于浏览器地址栏或跳转的完整 URL。encodeURIComponent的目标是安全地编码 URL 的片段(如查询参数),确保参数值中的特殊字符不会破坏 URL 的语法。
使用场景
-
使用
encodeURI的场景:- 直接对完整 URL 进行编码,例如
location.href = encodeURI("http://example.com/path?name=John Doe")。 - 编码后的 URL 可直接用于链接跳转,无需额外解码。
- 直接对完整 URL 进行编码,例如
-
使用
encodeURIComponent的场景:- 编码 URL 参数值,例如构造查询字符串:
"http://example.com/search?q=" + encodeURIComponent("JavaScript & Web Development")。 - 当参数值包含
&,?,=等字符时,必须使用encodeURIComponent防止解析错误。
- 编码 URL 参数值,例如构造查询字符串:
示例对比
encodeURI示例:
encodeURI("http://example.com/path?name=John Doe")
// 输出: "http://example.com/path?name=John%20Doe"(保留 `?` 和 `=`)
encodeURIComponent示例:
encodeURIComponent("http://example.com/path?name=John Doe")
// 输出: "http%3A%2F%2Fexample.com%2Fpath%3Fname%3DJohn%20D
二、HTTP、HTTPS
0、HTTP是哪一层的协议?
应用层。
1、谈一谈HTTP协议优缺点
HTTP 特点
- 「灵活可扩展」。一个是语法上只规定了基本格式,空格分隔单词,换行分隔字段等。另外一个就是传输形式上不仅可以传输文本,还可以传输图片,视频等任意数据。
- 「请求-应答模式」,通常而言,就是一方发送消息,另外一方要接受消息,或者是做出相应等。
- 「可靠传输」,HTTP是基于TCP/IP,因此把这一特性继承了下来。
- 「无状态」,这个分场景回答即可。
HTTP 缺点
- 「无状态」,有时候,需要保存信息,比如像购物系统,需要保留下顾客信息等等,另外一方面,有时候,无状态也会减少网络开销,比如类似直播行业这样子等,这个还是分场景来说。
- 「明文传输」,即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。这让HTTP的报文信息暴露给了外界,给攻击者带来了便利。
- 「队头阻塞」,当http开启长连接时,共用一个TCP连接,当某个请求时间过长时,其他的请求只能处于阻塞状态,这就是队头阻塞问题。
2、说一说HTTP 的请求方法?
1. GET
-
定义:请求服务器获取指定资源(如网页、图片、数据等)。
-
核心特点:
- 仅用于 “读取” 资源,不修改服务器数据(幂等性:多次请求结果一致)。
- 请求参数附加在 URL 中(如
?id=1&name=test),长度有限制(受浏览器 / 服务器限制)。 - 可被缓存、收藏,安全性较低(参数暴露在 URL 中)。
-
典型场景:浏览网页、搜索内容(如
GET /search?keyword=xxx)。
2. POST
-
定义:向服务器提交数据,用于创建或修改资源。
-
核心特点:
- 数据放在请求体(Body)中,长度无限制(适合传输大量数据,如表单、文件)。
- 不幂等(多次提交可能产生不同结果,如重复下单)。
- 不可被缓存、收藏,参数不暴露在 URL 中,安全性较高。
-
典型场景:提交表单(登录、注册)、上传文件、创建数据(如
POST /users新增用户)。
3. PUT
-
定义:向服务器发送数据,用于全量更新指定资源(若资源不存在则创建)。
-
核心特点:
- 幂等(多次请求结果一致,如多次更新同一用户的完整信息)。
- 需指定资源的唯一标识(如 URL 中的 ID),请求体包含资源的完整数据。
-
典型场景:修改用户的全部信息(如
PUT /users/123,请求体包含用户姓名、年龄等所有字段)。
4. PATCH
-
定义:向服务器发送数据,用于部分更新指定资源(仅修改资源的部分字段)。
-
核心特点:
- 非幂等(部分场景下可能不幂等,如增量更新数量)。
- 请求体仅包含需要修改的字段,比 PUT 更高效。
-
典型场景:仅修改用户的手机号(如
PATCH /users/123,请求体仅含{phone: "138xxx"})。
5. DELETE
-
定义:请求服务器删除指定资源。
-
核心特点:
- 幂等(多次删除同一资源,结果一致:第一次删除成功,后续返回 “不存在”)。
- 需要指定资源的唯一标识(如 URL 中的 ID)。
-
典型场景:删除用户(如
DELETE /users/123)、删除订单。
6. HEAD
-
定义:与 GET 类似,但仅请求资源的响应头(不返回响应体)。
-
核心特点:
- 用于获取资源的元信息(如文件大小、修改时间),不传输实际数据,节省带宽。
- 幂等、可缓存。
-
典型场景:检查文件是否存在、获取资源的更新时间(如
HEAD /files/report.pdf)。
7. 补充说明(体现深度)
- 除上述方法外,还有
OPTIONS(获取服务器支持的请求方法)、TRACE(用于调试,回显请求信息)等,但实际开发中较少使用。 - 理解 HTTP 方法的语义很重要:例如,GET/PUT/DELETE 的幂等性可用于重试机制设计,POST 的非幂等性需注意防重复提交(如加令牌)。
3、谈一谈GET 和 POST 的区别
-
请求目的与语义
- GET 用于获取资源(如查询数据),是 “读取” 操作,具有幂等性(多次请求结果一致,不改变服务器状态)。
- POST 用于提交资源(如表单提交、创建数据),是 “写入” 操作,不保证幂等性(多次提交可能产生不同结果,如重复下单)。
例:用 GET 请求查询商品详情,用 POST 请求提交订单。
-
数据传输方式
- GET 的参数通过URL 传递(格式为
?key1=value1&key2=value2),可见于地址栏,易被浏览器缓存、日志记录。 - POST 的参数通过请求体(Body) 传递,不可见(但可通过工具查看),默认不被缓存。
- GET 的参数通过URL 传递(格式为
-
数据长度限制
- GET 受限于 URL 长度(不同浏览器 / 服务器有差异,通常约 2KB),不适合传输大量数据。
- POST 无明确长度限制(由服务器配置决定),适合传输表单、文件等大容量数据。
-
安全性(相对)
- GET 参数暴露在 URL 中,可能被缓存、历史记录捕获,不适合传输密码、令牌等敏感信息。
- POST 参数在请求体中,相对隐蔽(但仍需通过 HTTPS 加密,否则可被抓包获取)。
-
浏览器行为
- 刷新 GET 请求页面,浏览器通常直接重发请求(因幂等性,无副作用)。
- 刷新 POST 请求页面,浏览器会提示 “是否重新提交”(因可能产生重复操作,如二次支付)。
GET 和 POST 的本质区别在于语义和使用场景:GET 是 “查”,POST 是 “改”。 其他差异(如参数位置、长度)是技术实现导致的副产品,而非设计初衷。实际开发中需根据语义选择,而非仅依赖技术细节(例如,不能为了 “隐藏参数” 而用 POST 查询数据)。
4、介绍一下HTTPS和HTTP区别
回答时,应从安全性核心差异出发,延伸到技术实现、端口、性能等维度,既讲清本质区别,又体现对网络安全的理解。
**核心差异**
HTTP(超文本传输协议)和 HTTPS(超文本传输安全协议)的核心差异在于安全性。HTTP 是明文传输协议,数据在传输过程中可被轻易窃听、篡改;HTTPS 则是在 HTTP 基础上加入了 SSL/TLS 加密层,通过加密、认证机制确保数据传输的机密性和完整性,是目前主流的安全传输协议。
**具体技术差异(按重要性排序)**
1. 数据传输安全性
-
HTTP:
数据以明文形式在网络中传输,中间人可直接窃听内容(如账号密码、交易信息),也可篡改数据(如修改订单金额),且无法验证服务器身份(易遭受 “钓鱼攻击”)。 -
HTTPS:
通过SSL/TLS 协议实现三层安全保障:- 机密性:使用对称加密算法(如 AES)加密传输数据,只有客户端和服务器能解密(通过非对称加密交换对称密钥)。
- 完整性:通过哈希算法(如 SHA)校验数据,一旦被篡改会被检测到。
- 身份认证:服务器需配置 CA 颁发的数字证书,客户端可验证服务器身份,防止钓鱼网站冒充。
2. 技术实现原理
- HTTP:直接基于 TCP 协议传输,协议栈为 “应用层(HTTP)→传输层(TCP)→网络层→链路层”。
- HTTPS:在 HTTP 与 TCP 之间增加了SSL/TLS 加密层,协议栈为 “应用层(HTTP)→安全层(SSL/TLS)→传输层(TCP)→...”。
简单说,HTTPS = HTTP + SSL/TLS,所有 HTTP 数据在传输前会先经过 SSL/TLS 加密。
3. 端口与默认配置
- HTTP:默认使用80 端口(如
http://example.com:80可简写为http://example.com)。 - HTTPS:默认使用443 端口(如
https://example.com:443可简写为https://example.com)。
4. 性能与资源消耗
- HTTP:无需加密解密过程,传输效率更高,服务器资源消耗更少。
- HTTPS:因增加了 SSL/TLS 握手(密钥交换、证书验证)和数据加解密步骤,会产生额外性能开销(通常增加 10%-30% 延迟),且服务器需消耗更多 CPU 资源。
5. 证书与成本
- HTTP:无需证书,部署零成本。
- HTTPS:服务器必须配置由权威 CA 机构(如 Let's Encrypt、DigiCert)颁发的数字证书,部分 CA 机构收费(如企业级证书),但也有免费证书(如 Let's Encrypt)可供个人或小型网站使用。
6、应用场景
- HTTP 适用场景:对安全性要求极低的静态资源(如公开文档),但目前已极少使用,主流浏览器会对 HTTP 网站标记 “不安全”。
- HTTPS 适用场景:几乎所有场景,尤其是涉及用户隐私(登录、支付、个人信息)、交易数据的网站,已成为行业标准(如电商、银行、社交平台等)。
核心结论:HTTPS 通过加密和认证解决了 HTTP 的安全缺陷,是互联网数据传输的安全基础,虽然存在一定性能和部署成本,但相比安全风险,这些成本在绝大多数场景下是必要的。
5、介绍一下Connection:keep-alive
1、什么是keep-alive
我们知道HTTP协议采用“请求-应答”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成 之后立即断开连接(HTTP协议为无连接的协议);
当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
2、为什么要使用keep-alive
keep-alive技术的创建目的,能在多次HTTP之前重用同一个TCP连接,从而减少创建/关闭多个 TCP 连接的开销(包括响应时间、CPU 资源、减少拥堵等),参考如下示意图(来源:维基百科):
3、客户端如何开启
在HTTP/1.0协议中,默认是关闭的,需要在http头加入"Connection: Keep-Alive”,才能启用Keep-Alive;
Connection: keep-alive
http 1.1中默认启用Keep-Alive,如果加入"Connection: close “,才关闭。
Connection: close
目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看服务器设置情况。
6、HTTP1.0、HTTP1.1核心区别
1. 连接管理机制(最核心差异)
- HTTP 1.0:默认使用 “短连接” (非持久连接)。
每次请求 - 响应完成后,TCP 连接立即关闭。若一个页面包含多个资源(如图片、CSS),需建立多次 TCP 连接,频繁的 “三次握手”“四次挥手” 会显著增加延迟。 - HTTP 1.1:默认使用 “长连接” (持久连接,
Connection: keep-alive)。
一次 TCP 连接可处理多个请求 - 响应(通过Keep-Alive头部控制超时时间),减少连接建立 / 关闭的开销,尤其适合包含大量资源的页面。
2. 缓存机制优化
-
HTTP 1.0:仅支持
Expires头部(基于绝对时间控制缓存过期),存在客户端与服务器时间不一致导致的缓存失效问题。 -
HTTP 1.1:
- 新增
Cache-Control头部(如max-age=3600,基于相对时间控制缓存,优先级高于Expires),解决时间同步问题。 - 新增
ETag(资源唯一标识)和If-None-Match头部,支持 “增量验证”(仅当资源变化时才传输完整内容,否则返回 304 Not Modified),减少带宽消耗。
- 新增
3. 请求处理能力
-
HTTP 1.0:一个 TCP 连接同时只能处理一个请求,需等待前一个请求响应完成后才能发送下一个(“队头阻塞” 问题初现)。
-
HTTP 1.1:
- 支持 “管道化(Pipelining)”:客户端可在收到前一个响应前,向服务器连续发送多个请求,减少等待时间(但因浏览器实现复杂,实际应用较少)。
- 新增
Host头部:使一台服务器可托管多个域名(虚拟主机),例如Host: www.example.com和Host: blog.example.com可对应同一服务器的不同网站,解决了 1.0 中一台服务器只能对应一个域名的限制。
4. 状态码与错误处理
-
HTTP 1.0:状态码较少(如 200、404、500 等基础码)。
-
HTTP 1.1:新增 24 个状态码,细化错误场景:
- 409 Conflict(资源冲突,如并发修改)
- 410 Gone(资源永久删除,区别于 404)
- 100 Continue(客户端可先发送请求头,服务器允许后再发送请求体,适合大文件上传)
5. 其他功能扩展
- 范围请求:HTTP 1.1 通过
Range头部支持断点续传(如Range: bytes=0-1023),可只请求资源的部分内容,1.0 不支持,会导致资源浪费。 - 更严格的协议规范:1.1 明确了请求方法的语义(如 PUT 的幂等性)、头部字段的定义,减少了实现差异。
6、核心改进
HTTP 1.1 的核心目标是提升性能和可靠性:通过长连接减少连接开销,通过完善的缓存机制降低带宽消耗,通过 Host 头部支持虚拟主机,这些改进为现代 Web 应用的发展奠定了基础。
7、HTTP1.1 、HTTP2.0核心区别
1. 传输格式:文本协议 vs 二进制分帧(最本质差异)
-
HTTP 1.1:基于文本协议,请求 / 响应以明文传输,解析时需处理换行符、空格等分隔符,易出错且效率低。
-
HTTP 2.0:采用二进制分帧层,将所有数据分割为更小的 “帧”(Frame),每个帧包含头部信息(如流 ID、类型)和二进制数据。
优势:解析更快(二进制无需处理文本歧义)、压缩效率更高、可在帧级别实现优先级控制。
2. 连接复用:串行请求 vs 多路复用
-
HTTP 1.1:存在 “队头阻塞(Head-of-Line Blocking)” 问题:
- 即使使用长连接,同一 TCP 连接中请求需串行发送(前一个请求未响应,后一个请求需等待)。
- 为缓解此问题,浏览器通常对同一域名建立 6-8 个并行 TCP 连接,但仍受限于 TCP 慢启动、连接开销等问题。
-
HTTP 2.0:通过 “多路复用(Multiplexing)” 彻底解决队头阻塞:
- 一个 TCP 连接中可同时传输多个请求 / 响应,每个请求对应一个 “流(Stream)”,通过帧的 “流 ID” 区分归属。
- 不同流的帧可交错传输,接收端通过流 ID 重组数据,实现并行处理,无需建立多个 TCP 连接。
3. 头部压缩:无专门压缩 vs HPACK 算法
-
HTTP 1.1:请求头(如 Cookie、User-Agent)以文本形式重复传输,未压缩,占带宽比例高(尤其大量小请求场景)。
-
HTTP 2.0:使用HPACK 压缩算法优化头部传输:
- 客户端和服务器维护一份 “静态字典”(常用头部如
Host、User-Agent的预设键值对)和 “动态字典”(会话中频繁出现的自定义头部)。 - 传输时仅发送字典索引或差异值,减少头部体积(实测可压缩 50%-90%)。
- 客户端和服务器维护一份 “静态字典”(常用头部如
4. 服务器推送(Server Push)
-
HTTP 1.1:服务器只能被动响应客户端请求,无法主动推送资源。例如,客户端请求 HTML 后,需解析页面再逐一请求 CSS、JS,增加延迟。
-
HTTP 2.0:支持服务器推送:
- 服务器可预判客户端需要的资源(如 HTML 引用的 CSS),在响应初始请求时主动推送关联资源,无需等待客户端显式请求。
- 例:客户端请求
index.html,服务器同时推送style.css和app.js,减少 “请求 - 响应” 往返次数。
5. 流优先级控制
-
HTTP 1.1:无法指定请求优先级,所有请求在连接中按顺序处理,关键资源(如 CSS)可能被非关键资源(如图片)阻塞。
-
HTTP 2.0:每个流可设置优先级(权重 + 依赖关系):
- 客户端可标记 “CSS 流” 优先级高于 “图片流”,服务器优先处理高优先级帧,减少页面渲染阻塞。
6、差异总结
- 安全性:HTTP 2.0 通常与 HTTPS 绑定(主流浏览器仅支持 TLS 加密的 HTTP 2.0),而 HTTP 1.1 可明文传输。
- 兼容性:HTTP 2.0 完全兼容 HTTP 1.1 的语义(请求方法、状态码、URI 等不变),仅改变传输层实现,无需修改应用层逻辑。
核心价值:HTTP 2.0 通过二进制分帧和多路复用,从根本上解决了 HTTP 1.1 的队头阻塞问题,结合头部压缩和服务器推送,大幅降低了延迟、减少了带宽消耗,为复杂 Web 应用(如移动端、单页应用)提供了性能支撑。
8、http协议无状态中的 "状态" 到底指的是什么?
每次 HTTP 请求都是独立的、无关的,默认不需要保存上下文信息。这里的无状态特指协议层面的无状态,应用层可以通过 session 会话信息保持状态。
无状态协议(Stateless Protocol) 就是指浏览器对于事务的处理没有记忆能力。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,但是服务器并不知道客户关闭了一次浏览器。
HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 小甜饼(Cookie) 的机制。它能够让浏览器具有记忆能力。
如果你的浏览器允许 cookie 的话,查看方式 chrome://settings/content/cookies
也就说明你的记忆芯片通电了...... 当你想服务端发送请求时,服务端会给你发送一个认证信息,服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束;
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。这样,你的浏览器才具有了记忆能力。
还有一种方式是使用 JWT 机制,它也是能够让你的浏览器具有记忆能力的一种机制。与 Cookie 不同,JWT 是保存在客户端的信息,它广泛的应用于单点登录的情况。JWT 具有两个特点
-
JWT 的 Cookie 信息存储在
客户端,而不是服务端内存中。也就是说,JWT 直接本地进行验证就可以,验证完毕后,这个 Token 就会在 Session 中随请求一起发送到服务器,通过这种方式,可以节省服务器资源,并且 token 可以进行多次验证。 -
JWT 支持跨域认证,Cookies 只能用在
单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是我们常说的跨域认证。
9、HTTP/1.1的keep-alive与HTTP/2多路复用的区别
- HTTP/1.x 是基于文本的,只能整体去传;HTTP/2 是基于二进制流的,可以分解为独立的帧,交错发送。
- HTTP/1.x keep-alive 必须按照请求发送的顺序返回响应;HTTP/2 多路复用不按序响应。
- HTTP/1.x keep-alive 为了解决队头阻塞,将同一个页面的资源分散到不同域名下,开启了多个 TCP 连接;HTTP/2 同域名下所有通信都在单个连接上完成。
- HTTP/1.x keep-alive 单个 TCP 连接在同一时刻只能处理一个请求(两个请求的生命周期不能重叠);HTTP/2 单个 TCP 同一时刻可以发送多个请求和响应。
昨天被问HTTP/1.1的 keep-alive 与HTTP/2多路复用的区别,我懵了
10、讲一讲对http2的了解,服务器推送SSE了解吗,浏览器是怎么接收怎么处理的?
HTTP/2 是 HTTP 协议的第二个主要版本,于 2015 年发布。它旨在解决 HTTP/1.1 的局限性,提升 Web 性能。以下是 HTTP/2 的核心特性,以及 服务器推送(Server Push) 的工作原理和浏览器处理方式。
http1.1主要问题:带宽跑不满 ,http1.1 对带宽的利用率低下原因:
1、TCP的慢启动:tcp协议采用了一个由慢到快的传输加速度
2、同时开启多个TCP链接,这些链接之间相互竞争带宽,影响关键资源的下载速度
3、http队头阻塞问题
http2.0里提出来的解决方案:
多路复用
1、不允许同时建立多个TCP连接,一个域名只能建立一个tcp连接
2、多路复用的流程:采用二进制分帧层1,将所有的请求处理为一帧一帧的请求,每一帧请求都带上独特的ID编号,服务端根据id区分拼接完整请求体,并以同样的方式返回响应
1. HTTP/2 的核心特性
(1) 二进制帧层
- HTTP/2 将请求和响应分解为二进制帧(Frame),而不是 HTTP/1.1 的文本格式。
- 帧是 HTTP/2 的最小通信单位,包含帧头(Header)和帧体(Payload)。
- 二进制帧更高效,解析速度更快。
(2) 多路复用(Multiplexing)
- HTTP/2 允许在同一个 TCP 连接上并行发送多个请求和响应。
- 解决了 HTTP/1.1 的队头阻塞(Head-of-Line Blocking)问题,提升了并发性能。
(3) 头部压缩(Header Compression)
- 使用 HPACK 算法压缩 HTTP 头部,减少数据传输量。
- 重复的头部字段只需发送一次,后续请求通过索引引用。
(4) 服务器推送(Server Push)
- 服务器可以在客户端请求之前,主动将资源推送到客户端。
- 减少客户端请求的往返时间(RTT),提升页面加载速度。
(5) 流优先级(Stream Prioritization)
- 客户端可以为请求设置优先级,服务器根据优先级处理请求。
- 确保关键资源(如 CSS、JavaScript)优先加载。
2. HTTP/2 服务器推送的工作原理
(1) 什么是服务器推送?
- 服务器推送允许服务器在客户端明确请求之前,主动将资源推送到客户端。
- 例如,当客户端请求 HTML 文件时,服务器可以主动推送与该 HTML 文件相关的 CSS、JavaScript 文件。
(2) 服务器推送的优势
- 减少客户端请求的往返时间(RTT)。
- 提前加载关键资源,提升页面加载速度。
(3) 服务器推送的实现
- 服务器在响应客户端请求时,通过 PUSH_PROMISE 帧通知客户端即将推送的资源。
- 客户端可以选择接受或拒绝推送的资源。
3. 浏览器如何处理服务器推送
(1) 接收推送资源
-
当服务器发送
PUSH_PROMISE帧时,浏览器会检查缓存。- 如果资源已经在缓存中,浏览器会拒绝推送。
- 如果资源不在缓存中,浏览器会接受推送,并将资源存储在缓存中。
(2) 使用推送资源
- 当浏览器解析 HTML 文件时,如果发现需要某个资源(如 CSS、JavaScript),会优先使用服务器推送的资源。
- 如果推送的资源未被使用,浏览器会将其丢弃。
(3) 推送资源的缓存
- 推送的资源会被存储在浏览器的缓存中,供后续请求使用。
- 如果资源已经过期,浏览器会重新请求。
4. 服务器推送的示例
(1) 服务器端配置
以 Nginx 为例,配置 HTTP/2 服务器推送:
server {
listen 443 ssl http2;
server_name example.com;
location / {
http2_push /style.css;
http2_push /script.js;
root /var/www/html;
}
}
(2) 客户端请求
客户端请求 index.html:
```js
GET /index.html HTTP/2
```
服务器响应并推送 style.css 和 script.js:
```js
HTTP/2 200 OK
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
<html>
<link rel="stylesheet" href="/style.css">
<script src="/script.js"></script>
</html>
```
5. 服务器推送的注意事项
(1) 推送的资源可能被浪费
如果客户端已经缓存了资源,推送的资源会被拒绝。
如果推送的资源未被使用,会增加带宽消耗。
(2) 推送的资源需要合理选择
只推送关键资源(如 CSS、JavaScript),避免推送不必要的资源。
使用 rel=preload 提示浏览器资源的优先级。
(3) 推送的资源需要缓存
推送的资源应设置合适的缓存策略,避免重复推送。
6. 小结
HTTP/2 的核心特性
二进制帧层、多路复用、头部压缩、服务器推送、流优先级。
服务器推送的工作原理
服务器通过 PUSH_PROMISE 帧主动推送资源。
浏览器根据缓存状态决定是否接受推送。
浏览器处理服务器推送
检查缓存,接受或拒绝推送资源。
使用推送资源,存储在缓存中供后续请求使用。
服务器推送的优势
减少客户端请求的往返时间,提升页面加载速度。
注意事项
推送的资源可能被浪费,需要合理选择和缓存。
通过合理使用 HTTP/2 的服务器推送功能,可以显著提升 Web 应用的性能。
11、介绍下HTTPS
HTTPS即超文本传输安全协议,是在 HTTP 协议基础上加入加密层的安全版本,旨在通过加密传输保护客户端与服务器之间的数据安全。它基于 SSL(Secure Sockets Layer)或 TLS(Transport Layer Security)协议实现加密,是目前互联网中保护敏感信息(如登录凭证、支付数据等)传输的主流标准。
HTTPS 的核心原理
HTTPS 并非独立协议,而是 “HTTP + 加密层(TLS/SSL)” 的结合体。其核心逻辑是:在客户端与服务器建立连接时,先通过 TLS/SSL 协议协商加密方式、交换密钥,随后所有 HTTP 数据都通过协商好的加密方式传输,确保数据在传输过程中不被窃取或篡改。
具体流程可简化为:
- 握手阶段:客户端与服务器交换证书、协商加密算法(如 RSA、ECDHE),生成会话密钥。
- 加密传输:后续所有 HTTP 请求和响应都使用会话密钥进行加密,第三方无法解密或篡改。
HTTPS 的核心特点(与 HTTP 对比)
| 特点 | HTTP | HTTPS |
|---|---|---|
| 安全性 | 明文传输,数据易被窃取 / 篡改 | 加密传输,数据机密性和完整性有保障 |
| 端口 | 默认使用 80 端口 | 默认使用 443 端口 |
| 证书 | 无需证书 | 需由 CA(证书颁发机构)签发的 SSL 证书 |
| 性能 | 无加密开销,速度较快 | 加密解密过程会增加少量性能开销 |
| 适用场景 | 非敏感数据(如公开新闻) | 敏感数据(如登录、支付、个人信息) |
HTTPS 的加密方式
HTTPS 采用混合加密机制(非对称加密 + 对称加密),兼顾安全性和效率:
-
非对称加密(握手阶段):
- 服务器拥有一对密钥:公钥(公开)和私钥(保密)。
- 客户端用服务器公钥加密 “会话密钥” 并发送,只有服务器的私钥能解密,确保会话密钥安全。
-
对称加密(数据传输阶段):
- 客户端与服务器使用握手阶段协商的 “会话密钥”(对称密钥)加密后续所有数据。
- 对称加密效率远高于非对称加密,适合大量数据传输。
HTTPS 的证书作用
SSL 证书是 HTTPS 安全的核心凭证,由权威 CA(如 Let’s Encrypt、DigiCert)签发,包含:
-
服务器域名、公钥、证书有效期、CA 签名等信息。
-
作用:
- 验证服务器身份(防止 “中间人攻击”,确保客户端连接的是真实服务器)。
- 提供服务器公钥,用于握手阶段的加密协商。
若证书无效(如过期、域名不匹配、未被信任 CA 签发),浏览器会提示 “不安全” 警告。
HTTPS 的优势
- 数据机密性:加密传输可防止数据被窃听(如公共 Wi-Fi 中的监听)。
- 数据完整性:通过哈希算法校验数据,确保传输中未被篡改。
- 身份认证:通过证书验证服务器真实性,避免钓鱼网站。
- 搜索引擎友好:Google 等搜索引擎优先收录 HTTPS 网站,影响排名。
HTTPS 通过加密、认证和完整性校验,解决了 HTTP 明文传输的安全漏洞,是现代互联网中保护用户数据安全的基础协议。 尽管存在少量性能开销,但在隐私保护日益重要的今天,已成为网站(尤其是涉及用户敏感信息的平台)的标配。
TLS/SSL的工作原理
TLS和 SSL是用于在网络通信中提供加密、身份认证和数据完整性的安全协议(TLS 是 SSL 的后续版本,目前主流使用 TLS 1.2/1.3)。它们的核心作用是在客户端和服务器之间建立一个安全的加密通道,确保数据传输不被窃听、篡改或伪造。
TLS/SSL 的核心目标
- 机密性:通过加密算法将数据转为密文,只有通信双方能解密。
- 完整性:确保数据在传输中未被篡改(通过哈希校验实现)。
- 身份认证:验证通信对方的真实身份(主要通过证书验证服务器,可选验证客户端)。
TLS/SSL 的工作流程(以 TLS 1.2 为例)
TLS/SSL 的工作过程可分为两大阶段:握手阶段(协商加密参数、交换密钥)和数据传输阶段(用协商的密钥加密通信)。
1. 握手阶段(核心步骤)
握手阶段是 TLS/SSL 最复杂的部分,目的是让客户端和服务器协商出一个会话密钥(用于后续对称加密),并验证服务器身份。以下是简化流程:
-
步骤 1:客户端发起 “客户端问候”(Client Hello)
客户端向服务器发送:- 支持的 TLS 版本(如 TLS 1.2)。
- 支持的加密套件列表(如
ECDHE-RSA-AES256-GCM-SHA384,包含密钥交换算法、对称加密算法、哈希算法)。 - 一个随机数(
Client Random),用于后续生成会话密钥。 - 其他可选信息(如压缩方法)。
-
步骤 2:服务器回应 “服务器问候”(Server Hello)
服务器从客户端的选项中选择合适的配置并返回:- 确认的 TLS 版本。
- 选定的加密套件(如
ECDHE-RSA-AES256-GCM-SHA384)。 - 服务器生成的随机数(
Server Random)。
-
步骤 3:服务器发送证书(Certificate)
服务器向客户端发送SSL 证书(由权威 CA 签发),包含:- 服务器的公钥(用于后续加密)。
- 证书持有者信息(如域名)、有效期、CA 签名等。
客户端会验证证书的有效性(如是否过期、域名是否匹配、是否被信任的 CA 签发),若无效则提示风险。
-
步骤 4:服务器发送 “服务器问候完成”(Server Hello Done)
告知客户端:服务器的问候信息已发送完毕,等待客户端回应。 -
步骤 5:客户端验证证书并生成 “预主密钥”(Pre-Master Secret)
客户端验证证书通过后:- 生成一个随机数
Pre-Master Secret(预主密钥)。 - 用服务器证书中的公钥加密
Pre-Master Secret,发送给服务器(只有服务器的私钥能解密)。
- 生成一个随机数
-
步骤 6:客户端和服务器生成 “会话密钥”
客户端和服务器分别使用已交换的三个随机数(Client Random、Server Random、Pre-Master Secret),通过加密套件中约定的算法,生成相同的会话密钥(对称密钥,用于后续数据加密)。 -
步骤 7:客户端发送 “完成通知”(Client Finished)
客户端用会话密钥加密一段 “结束信息”(包含握手阶段的所有数据哈希值),发送给服务器,证明握手未被篡改,且客户端已准备好加密通信。 -
步骤 8:服务器发送 “完成通知”(Server Finished)
服务器用会话密钥解密客户端的 “完成通知”,验证通过后,同样加密一段 “结束信息” 发送给客户端,证明服务器也已准备好。
至此,握手阶段结束,双方已确认会话密钥一致,且握手过程未被篡改。
2. 数据传输阶段
握手完成后,客户端和服务器使用会话密钥(对称加密算法,如 AES)对所有后续通信数据进行加密和解密。同时,通过哈希算法(如 SHA-256)生成数据的校验值,确保数据传输中未被篡改(若篡改,校验值会不匹配)。
TLS 1.3 的优化(与 TLS 1.2 对比)
TLS 1.3 是目前最新的版本,简化了握手流程,提升了安全性和效率:
- 减少握手次数:将握手从 “2-RTT”(往返次数)缩短到 “1-RTT”,甚至首次连接可实现 “0-RTT”(复用之前的会话信息)。
- 移除不安全加密套件:禁用了 SHA-1、RC4 等弱加密算法,仅保留更安全的选项(如 ECDHE 密钥交换、AES-GCM 对称加密)。
- 简化流程:合并了部分步骤(如服务器问候和证书发送可并行),减少数据传输量。
核心总结
TLS/SSL 通过 “非对称加密交换密钥,对称加密传输数据” 的混合模式,兼顾了安全性和效率:
- 握手阶段用非对称加密(服务器公钥加密预主密钥)确保密钥安全交换。
- 数据传输阶段用对称加密(会话密钥)高效加密大量数据。
- 证书用于验证服务器身份,防止 “中间人攻击”。
- 哈希校验确保数据传输的完整性。
SSL 连接断开后如何恢复?
一共有两种方法来恢复断开的 SSL 连接,一种是使用 session ID,一种是 session ticket。
1、通过session ID
使用 session ID 的方式,每一次的会话都有一个编号,当对话中断后,下一次重新连接时,只要客户端给出这个编号,服务器如果有这个编号的记录,那么双方就可以继续使用以前的秘钥,而不用重新生成一把。目前所有的浏览器都支持这一种方法。但是这种方法有一个缺点是,session ID 只能够存在一台服务器上,如果我们的请求通过负载平衡被转移到了其他的服务器上,那么就无法恢复对话。
2、通过session ticket
另一种方式是 session ticket 的方式,session ticket 是服务器在上一次对话中发送给客户的,这个 ticket 是加密的,只有服务器能够解密,里面包含了本次会话的信息,比如对话秘钥和加密方法等。这样不管我们的请求是否转移到其他的服务器上,当服务器将 ticket 解密以后,就能够获取上次对话的信息,就不用重新生成对话秘钥了。
12、谈一谈队头阻塞问题
什么是队头阻塞?
对于每一个HTTP请求而言,这些任务是会被放入一个任务队列中串行执行的,一旦队首任务请求太慢时,就会阻塞后面的请求处理,这就是HTTP队头阻塞问题。
解决办法:
并发连接
我们知道对于一个域名而言,是允许分配多个长连接的,那么可以理解成增加了任务队列,也就是说不会导致一个任务阻塞了该任务队列的其他任务,在RFC规范中规定客户端最多并发2个连接,不过实际情况就是要比这个还要多,举个例子,Chrome中是6个。
域名分片
顾名思义,我们可以在一个域名下分出多个二级域名出来,而它们最终指向的还是同一个服务器,这样子的话就可以并发处理的任务队列更多,也更好的解决了队头阻塞的问题。
举个例子,比如xxx.com,可以分出很多二级域名,比如Day1.xxx.com,Day2.xxx.com,Day3.xxx.com,这样子就可以有效解决队头阻塞问题。
13、谈一谈HTTP传输定长数据、不定长数据
大概遇到的情况就分为「定长数据」与「不定长数据」的处理吧。
定长数据
对于定长的数据包而言,发送端在发送数据的过程中,需要设置Content-Length,来指明发送数据的长度。
当然了如果采用了Gzip压缩的话,Content-Length设置的就是压缩后的传输长度。
我们还需要知道的是👇
- Content-Length如果存在并且有效的话,则必须和消息内容的传输长度完全一致,也就是说,如果过短就会截断,过长的话,就会导致超时。
- 如果采用短链接的话,直接可以通过服务器关闭连接来确定消息的传输长度。
- 那么在HTTP/1.0之前的版本中,Content-Length字段可有可无,因为一旦服务器关闭连接,我们就可以获取到传输数据的长度了。
- 在HTTP/1.1版本中,如果是Keep-alive的话,chunked优先级高于
Content-Length,若是非Keep-alive,跟前面情况一样,Content-Length可有可无。
那怎么来设置Content-Length
举个例子来看看👇
const server = require('http').createServer();
server.on('request', (req, res) => {
if(req.url === '/index') {
// 设置数据类型
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', 10);
res.write("你好,使用的是Content-Length设置传输数据形式");
}
})
server.listen(3000, () => {
console.log("成功启动--TinaTian");
})
不定长数据
现在采用最多的就是HTTP/1.1版本,来完成传输数据,在保存Keep-alive状态下,当数据是不定长的时候,我们需要设置新的头部字段👇
Transfer-Encoding: chunked
通过chunked机制,可以完成对不定长数据的处理,当然了,你需要知道的是
- 如果头部信息中有
Transfer-Encoding,优先采用Transfer-Encoding里面的方法来找到对应的长度。 - 如果设置了Transfer-Encoding,那么Content-Length将被忽视。
- 使用长连接的话,会持续的推送动态内容。
那我们来模拟一下吧👇
const server = require('http').createServer();
server.on('request', (req, res) => {
if(req.url === '/index') {
// 设置数据类型
res.setHeader('Content-Type', 'text/html; charset=utf8');
res.setHeader('Content-Length', 10);
res.setHeader('Transfer-Encoding', 'chunked');
res.write("你好,使用的是Transfer-Encoding设置传输数据形式");
setTimeout(() => {
res.write("第一次传输数据给您<br/>");
}, 1000);
res.write("骚等一下");
setTimeout(() => {
res.write("第一次传输数据给您");
res.end()
}, 3000);
}
})
server.listen(3000, () => {
console.log("成功启动--TinaTian");
})
上面使用的是nodejs中http模块,有兴趣的小伙伴可以去试一试,以上就是HTTP对「定长数据」和「不定长数据」传输过程中的处理手段。
14、介绍一下HTTPS和HTTP区别
HTTPS 要比 HTTPS 多了 secure 安全性这个概念,实际上, HTTPS 并不是一个新的应用层协议,它其实就是 HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 SSL/TLS 所做的工作。
「SSL」
安全套接层(Secure Sockets Layer)
「TLS」
(传输层安全,Transport Layer Security)
现在主流的版本是 TLS/1.2, 之前的 TLS1.0、TLS1.1 都被认为是不安全的,在不久的将来会被完全淘汰。
「HTTPS 就是身披了一层 SSL 的 HTTP」。
HTTP与HTTPS区别
那么区别有哪些呢👇
- HTTP 是明文传输协议,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
- HTTPS比HTTP更加安全,对搜索引擎更友好,利于SEO,谷歌、百度优先索引HTTPS网页。
- HTTPS标准端口443,HTTP标准端口80。
- HTTPS需要用到SSL证书,而HTTP不用。
我觉得记住以下两点HTTPS主要作用就行👇
- 对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全;
- 对网站服务器进行真实身份认证。
介绍一个HTTPS工作原理
上一节来看,我们可以把HTTPS理解成**「HTTPS = HTTP + SSL/TLS」**
TLS/SSL 的功能实现主要依赖于三类基本算法:
散列函数、对称加密和非对称加密,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
对称加密
加密和解密用同一个秘钥的加密方式叫做对称加密。Client客户端和Server端共用一套密钥,这样子的加密过程似乎很让人理解,但是随之会产生一些问题。
「问题一:」 WWW万维网有许许多多的客户端,不可能都用秘钥A进行信息加密,这样子很不合理,所以解决办法就是使用一个客户端使用一个密钥进行加密。
「问题二:「既然不同的客户端使用不同的密钥,那么」对称加密的密钥如何传输?」 那么解决的办法只能是**「一端生成一个秘钥,然后通过HTTP传输给另一端」**,那么这样子又会产生新的问题。
「问题三:」 这个传输密钥的过程,又如何保证加密?「如果被中间人拦截,密钥也会被获取,」 那么你会说对密钥再进行加密,那又怎么保存对密钥加密的过程,是加密的过程?
到这里,我们似乎想明白了,使用对称加密的方式,行不通,所以我们需要采用非对称加密👇
非对称加密
通过上面的分析,对称加密的方式行不通,那么我们来梳理一下非对称加密。采用的算法是RSA,所以在一些文章中也会看见**「传统RSA握手」,基于现在TLS主流版本是1.2,所以接下来梳理的是「TLS/1.2握手过程」**。
非对称加密中,我们需要明确的点是👇
- 有一对秘钥,「公钥」和「私钥」。
- 公钥加密的内容,只有私钥可以解开,私钥加密的内容,所有的公钥都可以解开,这里说的**「公钥都可以解开,指的是一对秘钥」**。
- 公钥可以发送给所有的客户端,私钥只保存在服务器端。
主要工作流程
梳理起来,可以把**「TLS 1.2 握手过程」**分为主要的五步👇
图片内容来自浪里行舟
步骤(1)
Client发起一个HTTPS请求,连接443端口。这个过程可以理解成是**「请求公钥的过程」**。
步骤(2)
Server端收到请求后,通过第三方机构私钥加密,会把数字证书(也可以认为是公钥证书)发送给Client。
步骤(3)
- 浏览器安装后会自动带一些权威第三方机构公钥,使用匹配的公钥对数字签名进行解密。
- 根据签名生成的规则对网站信息进行本地签名生成,然后两者比对。
- 通过比对两者签名,匹配则说明认证通过,不匹配则获取证书失败。
步骤(4)
在安全拿到**「服务器公钥」后,客户端Client随机生成一个「对称密钥」,使用「服务器公钥」(证书的公钥)加密这个「对称密钥」**,发送给Server(服务器)。
步骤(5)
Server(服务器)通过自己的私钥,对信息解密,至此得到了**「对称密钥」,此时两者都拥有了相同的「对称密钥」**。
接下来,就可以通过该对称密钥对传输的信息加密/解密啦,从上面图举个例子👇
- Client用户使用该**「对称密钥」**加密'明文内容B',发送给Server(服务器)
- Server使用该**「对称密钥」**进行解密消息,得到明文内容B。
接下来考虑一个问题,「如果公钥被中间人拿到纂改怎么办呢?」
以下图片来自leocoder
中间人获取公钥
「客户端可能拿到的公钥是假的,解决办法是什么呢?」
第三方认证
客户端无法识别传回公钥是中间人的,还是服务器的,这是问题的根本,我们是不是可以通过某种规范可以让客户端和服务器都遵循某种约定呢?那就是通过**「第三方认证的方式」**
在HTTPS中,通过 「证书」 + **「数字签名」**来解决这个问题。
这里唯一不同的是,假设对网站信息加密的算法是MD5,通过MD5加密后,「然后通过第三方机构的私钥再次对其加密,生成数字签名」。
这样子的话,数字证书包含有两个特别重要的信息👉**「某网站公钥+数字签名」**
我们再次假设中间人截取到服务器的公钥后,去替换成自己的公钥,因为有数字签名的存在,这样子客户端验证发现数字签名不匹配,这样子就防止中间人替换公钥的问题。
那么客户端是如何去对比两者数字签名的呢?
- 浏览器会去安装一些比较权威的第三方认证机构的公钥,比如VeriSign、Symantec以及GlobalSign等等。
- 验证数字签名的时候,会直接从本地拿到相应的第三方的公钥,对私钥加密后的数字签名进行解密得到真正的签名。
- 然后客户端利用签名生成规则进行签名生成,看两个签名是否匹配,如果匹配认证通过,不匹配则获取证书失败。
数字签名作用
数字签名:将网站的信息,通过特定的算法加密,比如MD5,加密之后,再通过服务器的私钥进行加密,形成**「加密后的数字签名」**。
第三方认证机构是一个公开的平台,中间人可以去获取。
如果没有数字签名的话,这样子可以就会有下面情况👇
从上面我们知道,如果**「只是对网站信息进行第三方机构私钥加密」**的话,还是会受到欺骗。
因为没有认证,所以中间人也向第三方认证机构进行申请,然后拦截后把所有的信息都替换成自己的,客户端仍然可以解密,并且无法判断这是服务器的还是中间人的,最后造成数据泄露。
「总结」
-
HTTPS就是使用SSL/TLS协议进行加密传输
-
大致流程:客户端拿到服务器的公钥(是正确的),然后客户端随机生成一个**「对称加密的秘钥」,使用「该公钥」加密,传输给服务端,服务端再通过解密拿到该「对称秘钥」,后续的所有信息都通过该「对称秘钥」**进行加密解密,完成整个HTTPS的流程。
-
「第三方认证」,最重要的是**「数字签名」**,避免了获取的公钥是中间人的。
15、HTTPS是什么?具体流程?
HTTPS 是在 HTTP 和 TCP 之间建立了一个安全层,HTTP 与 TCP 通信的时候,必须先进过一个安全层,对数据包进行加密,然后将加密后的数据包传送给 TCP,相应的 TCP 必须将数据包解密,才能传给上面的 HTTP。
浏览器传输一个 client_random 和加密方法列表,服务器收到后,传给浏览器一个 server_random、加密方法列表和数字证书(包含了公钥),然后浏览器对数字证书进行合法验证,如果验证通过,则生成一个 pre_random,然后用公钥加密传给服务器,服务器用 client_random、server_random 和 pre_random ,使用公钥加密生成 secret,然后之后的传输使用这个 secret 作为秘钥来进行数据的加解密。
16、介绍下http缓存
17、对 Accept 系列字段了解多少?
对于Accept系列字段的介绍分为四个部分: 数据格式、压缩方式、支持语言和字符集。
数据格式
上一节谈到 HTTP 灵活的特性,它支持非常多的数据格式,那么这么多格式的数据一起到达客户端,客户端怎么知道它的格式呢?
当然,最低效的方式是直接猜,有没有更好的方式呢?直接指定可以吗?
答案是肯定的。不过首先需要介绍一个标准——MIME(Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展)。它首先用在电子邮件系统中,让邮件可以发任意类型的数据,这对于 HTTP 来说也是通用的。
因此,HTTP 从MIME type取了一部分来标记报文 body 部分的数据类型,这些类型体现在Content-Type这个字段,当然这是针对于发送端而言,接收端想要收到特定类型的数据,也可以用Accept字段。
具体而言,这两个字段的取值可以分为下面几类:
- text: text/html, text/plain, text/css 等
- image: image/gif, image/jpeg, image/png 等
- audio/video: audio/mpeg, video/mp4 等
- application: application/json, application/javascript, application/pdf, application/octet-stream
压缩方式
当然一般这些数据都是会进行编码压缩的,采取什么样的压缩方式就体现在了发送方的Content-Encoding字段上, 同样的,接收什么样的压缩方式体现在了接受方的Accept-Encoding字段上。这个字段的取值有下面几种:
- gzip: 当今最流行的压缩格式
- deflate: 另外一种著名的压缩格式
- br: 一种专门为 HTTP 发明的压缩算法
// 发送端
Content-Encoding: gzip
// 接收端
Accept-Encoding: gzip
支持语言
对于发送方而言,还有一个Content-Language字段,在需要实现国际化的方案当中,可以用来指定支持的语言,在接受方对应的字段为Accept-Language。如:
// 发送端
Content-Language: zh-CN, zh, en
// 接收端
Accept-Language: zh-CN, zh, en
字符集
最后是一个比较特殊的字段, 在接收端对应为Accept-Charset,指定可以接受的字符集,而在发送端并没有对应的Content-Charset, 而是直接放在了Content-Type中,以charset属性指定。如:
// 发送端
Content-Type: text/html; charset=utf-8
// 接收端
Accept-Charset: charset=utf-8
最后以一张图来总结一下吧:
18、HTTP 如何处理大文件的传输?
对于几百 M 甚至上 G 的大文件来说,如果要一口气全部传输过来显然是不现实的,会有大量的等待时间,严重影响用户体验。因此,HTTP 针对这一场景,采取了范围请求的解决方案,允许客户端仅仅请求一个资源的一部分。
如何支持
当然,前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头:
Accept-Ranges: none
用来告知客户端这边是支持范围请求的。
Range 字段拆解
而对于客户端而言,它需要指定请求哪一部分,通过Range这个请求头字段确定,格式为bytes=x-y。接下来就来讨论一下这个 Range 的书写格式:
- 0-499表示从开始到第 499 个字节。
- 500- 表示从第 500 字节到文件终点。
- -100表示文件的最后100个字节。
服务器收到请求之后,首先验证范围是否合法,如果越界了那么返回416错误码,否则读取相应片段,返回206状态码。
同时,服务器需要添加Content-Range字段,这个字段的格式根据请求头中Range字段的不同而有所差异。
具体来说,请求单段数据和请求多段数据,响应头是不一样的。
举个例子:
// 单段数据
Range: bytes=0-9
// 多段数据
Range: bytes=0-9, 30-39
接下来我们就分别来讨论着两种情况。
单段数据
对于单段数据的请求,返回的响应如下:
HTTP/1.1 206 Partial Content
Content-Length: 10
Accept-Ranges: bytes
Content-Range: bytes 0-9/100
i am xxxxx
值得注意的是Content-Range字段,0-9表示请求的返回,100表示资源的总大小,很好理解。
多段数据
接下来我们看看多段请求的情况。得到的响应会是下面这个形式:
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000010101
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000010101
Content-Type: text/plain
Content-Range: bytes 0-9/96
i am xxxxx
--00000010101
Content-Type: text/plain
Content-Range: bytes 20-29/96
eex jspy e
--00000010101--
这个时候出现了一个非常关键的字段Content-Type: multipart/byteranges;boundary=00000010101,它代表了信息量是这样的:
- 请求一定是多段数据请求
- 响应体中的分隔符是 00000010101
因此,在响应体中各段数据之间会由这里指定的分隔符分开,而且在最后的分隔末尾添上--表示结束。
以上就是 http 针对大文件传输所采用的手段。
19、HTTP 中如何处理表单数据的提交?
在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:
- application/x-www-form-urlencoded
- multipart/form-data
由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中。
application/x-www-form-urlencoded
对于application/x-www-form-urlencoded格式的表单内容,有以下特点:
- 其中的数据会被编码成以
&分隔的键值对 - 字符以URL编码方式编码。
如:
// 转换过程: {a: 1, b: 2} -> a=1&b=2 -> 如下(最终形式)
"a%3D1%26b%3D2"
multipart/form-data
对于multipart/form-data而言:
- 请求头中的
Content-Type字段会包含boundary,且boundary的值有浏览器默认指定。例:Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe。 - 数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,如
Content-Type,在最后的分隔符会加上--表示结束。
相应的请求体是下面这样:
Content-Disposition: form-data;name="data1";
Content-Type: text/plain
data1
----WebkitFormBoundaryRRJKeWfHPGrS4LKe
Content-Disposition: form-data;name="data2";
Content-Type: text/plain
data2
----WebkitFormBoundaryRRJKeWfHPGrS4LKe--
小结
值得一提的是,multipart/form-data 格式最大的特点在于:每一个表单元素都是独立的资源表述。另外,你可能在写业务的过程中,并没有注意到其中还有boundary的存在,如果你打开抓包工具,确实可以看到不同的表单元素被拆分开了,之所以在平时感觉不到,是以为浏览器和 HTTP 给你封装了这一系列操作。
而且,在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。
20、HTTP常用的请求头、响应头有哪些?
HTTP Request Header 常见的请求头:
- Accept:浏览器能够处理的内容类型
- Accept-Charset:浏览器能够显示的字符集
- Accept-Encoding:浏览器能够处理的压缩编码
- Accept-Language:浏览器当前设置的语言
- Connection:浏览器与服务器之间连接的类型
- Cookie:当前页面设置的任何Cookie
- Host:发出请求的页面所在的域
- Referer:发出请求的页面的URL
- User-Agent:浏览器的用户代理字符串
HTTP Responses Header 常见的响应头:
- Date:表示消息发送的时间,时间的描述格式由rfc822定义
- server:服务器名称
- Connection:浏览器与服务器之间连接的类型
- Cache-Control:控制HTTP缓存
- content-type:表示后面的文档属于什么MIME类型
常见的 Content-Type 属性值有以下四种:
(1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。
(2)multipart/form-data:该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。
(3)application/json:服务器消息主体是序列化后的 JSON 字符串。
(4)text/xml:该种方式主要用来提交 XML 格式的数据。
三、OPTIONS请求
预检请求是什么?
预检请求(Preflight Request) 是浏览器在发送某些 跨域 HTTP 请求 之前,自动发起的一种 OPTIONS 请求。它的目的是确保服务器允许实际的跨域请求。预检请求是 CORS(跨域资源共享) 机制的一部分,用于保护服务器和客户端的安全。
为什么需要预检请求?
(1) 跨域请求的安全限制
- 浏览器遵循 同源策略(Same-Origin Policy) ,默认禁止跨域请求。
- 为了支持安全的跨域请求,CORS 机制被引入。
(2) 复杂请求的预检
- 对于 简单请求(如 GET、POST 请求,且使用特定的 Content-Type),浏览器会直接发送请求。
- 对于 复杂请求(如 PUT、DELETE 请求,或使用自定义的 HTTP 头),浏览器会先发送预检请求,确认服务器是否允许实际请求。
什么情况下会触发预检请求?
以下条件满足任意一个时,浏览器会发送预检请求:
请求方法不是 GET、POST 或 HEAD。
- 例如:PUT、DELETE、PATCH。
请求头包含自定义头。
- 例如:`Authorization`、`X-Custom-Header`。
Content-Type 不是以下值之一:
- `application/x-www-form-urlencoded`
- `multipart/form-data`
- `text/plain`
预检请求的工作流程
(1) 浏览器发送预检请求
浏览器发送一个 OPTIONS 请求 到服务器,包含以下头信息:
- `Origin`:请求的来源(协议 + 域名 + 端口)。
- `Access-Control-Request-Method`:实际请求的 HTTP 方法(如 PUT、DELETE)。
- `Access-Control-Request-Headers`:实际请求的自定义头(如 `Authorization`)。
(2) 服务器响应预检请求
服务器需要返回以下头信息:
- `Access-Control-Allow-Origin`:允许的源(如 `*` 或具体的域名)。
- `Access-Control-Allow-Methods`:允许的 HTTP 方法(如 `GET, POST, PUT`)。
- `Access-Control-Allow-Headers`:允许的自定义头(如 `Authorization`)。
- `Access-Control-Max-Age`:预检请求的缓存时间(单位:秒)。
(3) 浏览器发送实际请求
- 如果预检请求通过,浏览器会发送实际的跨域请求。
- 如果预检请求失败,浏览器会阻止实际请求,并抛出 CORS 错误。
预检请求示例
(1) 客户端代码
fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value',
},
body: JSON.stringify({ key: 'value' }),
});
(2) 预检请求(OPTIONS)
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header
(3) 服务器响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400
(4) 实际请求(PUT)
PUT /data HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
Content-Type: application/json
X-Custom-Header: value
{"key":"value"}
(5) 服务器响应实际请求
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json
{"status":"success"}
如何减少预检请求?
(1) 使用简单请求
- 尽量使用 GET、POST 方法,并避免自定义头。
- 使用
application/x-www-form-urlencoded、multipart/form-data或text/plain作为Content-Type。
(2) 缓存预检请求
通过设置 Access-Control-Max-Age 头,缓存预检请求的结果,减少重复的预检请求。
(3) 避免跨域请求
如果可能,将 API 和前端部署在同一个域名下,避免跨域问题。
总结
| 特性 | 预检请求(OPTIONS) | 实际请求(GET/POST/PUT 等) |
|---|---|---|
| 触发条件 | 复杂请求(自定义头、非简单方法等) | 简单请求或预检请求通过后 |
| 请求方法 | OPTIONS | GET、POST、PUT 等 |
| 目的 | 确认服务器是否允许实际请求 | 发送实际的跨域请求 |
| 响应头 | Access-Control-Allow-* 系列头 | 实际数据 |
预检请求是 CORS 机制的重要组成部分,用于确保跨域请求的安全性。通过合理配置服务器响应头,可以减少预检请求的频率,提升性能。
预检请求响应
预检请求(Preflight Request) 的响应状态码通常是 200 OK。这是因为预检请求的目的是确认服务器是否允许实际的跨域请求,而不是实际处理请求。服务器通过返回 200 OK 状态码和相关的 CORS 头信息,告诉浏览器是否允许后续的实际请求。
1. 预检请求的响应状态码
(1) 成功响应
如果服务器允许跨域请求,预检请求会返回 200 OK 状态码,并包含以下 CORS 头信息:
- `Access-Control-Allow-Origin`:允许的源(如 `*` 或具体的域名)。
- `Access-Control-Allow-Methods`:允许的 HTTP 方法(如 `GET, POST, PUT`)。
- `Access-Control-Allow-Headers`:允许的自定义头(如 `Authorization`)。
- `Access-Control-Max-Age`:预检请求的缓存时间(单位:秒)。
(2) 失败响应
如果服务器不允许跨域请求,预检请求可能会返回 403 Forbidden 或 405 Method Not Allowed 等状态码。
浏览器会根据预检请求的响应决定是否发送实际请求。
2. 预检请求的响应示例
(1) 成功响应
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
(2) 失败响应
HTTP/1.1 403 Forbidden
3. 小结
| 场景 | 状态码 | 说明 |
|---|---|---|
| 预检请求成功 | 200 OK | 服务器允许跨域请求,返回 CORS 头信息。 |
| 预检请求失败 | 403/405 等 | 服务器不允许跨域请求,浏览器会阻止实际请求。 |
预检请求的响应状态码通常是 200 OK,表示服务器允许跨域请求。如果服务器不允许跨域请求,可能会返回其他状态码(如 403 或 405)。
怎么设置取消预检?(深入解释)
在 HTTP 跨域请求中,预检请求(OPTIONS 请求) 是浏览器的一种安全机制,用于验证服务器是否允许实际请求(尤其是 “非简单请求”)。预检请求无法被 “完全取消”,但可以通过合理配置服务器和优化请求,避免触发预检或减少预检请求的频率。
什么情况下会触发预检请求?
浏览器对请求进行分类,只有 “非简单请求” 才会触发预检(OPTIONS):
简单请求(不触发预检)需同时满足:
- 请求方法:仅限
GET、HEAD、POST;
请求头 :仅包含浏览器默认头部。
如 Accept、Accept-Language、Content-Language
或Content-Type为以下之一:
- `application/x-www-form-urlencoded`
- `multipart/form-data`
- `text/plain`
无自定义头部(如 X-Token、Authorization 等,除非服务器明确允许)。
非简单请求(触发预检):
- 使用
PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH等方法; - 包含自定义头部(如
X-Requested-With、Authorization); Content-Type为application/json、application/xml等非简单类型;- 携带
withCredentials(跨域携带 Cookie)。
如何避免或减少预检请求?
1. 让请求符合 “简单请求” 条件(最直接)
-
使用
GET/POST/HEAD方法; -
不使用自定义头部(或仅用浏览器默认头部);
-
POST请求的Content-Type设为application/x-www-form-urlencoded或multipart/form-data(而非application/json)。示例:将 JSON 数据转为表单格式发送,避免
Content-Type: application/json:// 避免:Content-Type: application/json(会触发预检) fetch('https://api.example.com', { method: 'POST', headers: { 'Content-Type': 'application/json' }, // 非简单类型 body: JSON.stringify({ name: 'test' }) }); // 改为:Content-Type: application/x-www-form-urlencoded(简单请求) fetch('https://api.example.com', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ name: 'test' }) // 表单格式 });
2. 服务器配置:缓存预检结果(减少重复请求)
通过 Access-Control-Max-Age 头设置预检请求的缓存时间(单位:秒),浏览器在缓存有效期内不会重复发送预检。
配置示例(不同服务器):
-
Nginx:
location / { add_header Access-Control-Allow-Origin "https://your-domain.com"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE"; add_header Access-Control-Allow-Headers "X-Token, Content-Type"; add_header Access-Control-Max-Age 86400; # 缓存1天(86400秒) # 处理预检请求(直接返回204) if ($request_method = 'OPTIONS') { return 204; } } -
Node.js(Express) :
const express = require('express'); const app = express(); app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', 'https://your-domain.com'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.setHeader('Access-Control-Allow-Headers', 'X-Token, Content-Type'); res.setHeader('Access-Control-Max-Age', '86400'); // 缓存1天 // 处理预检请求 if (req.method === 'OPTIONS') { return res.sendStatus(204); } next(); }); -
Apache(.htaccess):
Header set Access-Control-Allow-Origin "https://your-domain.com" Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE" Header set Access-Control-Allow-Headers "X-Token, Content-Type" Header set Access-Control-Max-Age 86400 # 处理预检请求 <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=204,L] </IfModule>
3. 避免不必要的跨域场景
- 若前后端同源(域名、端口、协议一致),则不会触发跨域检查,自然无预检请求;
- 若必须跨域,可通过 “后端代理”(如 Nginx 反向代理)将跨域请求转为同源请求,绕过浏览器的跨域限制。
关键说明
- 预检请求是浏览器的行为,服务器无法完全禁用(除非不处理跨域请求);
- 合理配置
Access-Control-Max-Age可大幅减少预检请求的次数; - 对于必须使用非简单请求的场景(如
PUT方法、application/json类型),预检请求是必要的安全验证,无法避免。
通过以上方法,可以有效减少预检请求的触发频率,优化跨域请求性能。
四、状态码
HTTP 状态码是服务器对客户端请求的响应状态标识,由三位数字组成,分为 5 类(以第一位数字区分) 。以下是常见状态码的详细说明:
1xx:信息性状态码(请求已接收,继续处理)
表示服务器已接收请求,正在处理或需要进一步操作。
- 100 Continue:服务器已收到请求头,客户端可继续发送请求体(常用于大文件上传)。
- 101 Switching Protocols:服务器同意客户端的协议切换请求(如从 HTTP 切换到 WebSocket)。
2xx:成功状态码(请求已成功处理)
表示客户端请求被服务器正常接收并处理。
- 200 OK:请求成功,服务器返回对应数据(最常见状态码)。
- 201 Created:请求成功且服务器创建了新资源(如 POST 提交表单创建用户)。
- 204 No Content:请求成功,但服务器无数据返回(如 DELETE 删除资源后)。
- 206 Partial Content:客户端请求部分资源(如断点续传),服务器成功返回部分数据。
3xx:重定向状态码(需要进一步操作以完成请求)
表示客户端需要通过额外操作(如跳转)才能完成请求。
- 301 Moved Permanently:资源永久迁移到新 URL,浏览器会缓存新地址(下次直接访问新 URL)。
- 302 Found:资源临时迁移到新 URL,浏览器不会缓存新地址(每次请求仍需通过原 URL 跳转)。
- 304 Not Modified:资源未修改,客户端可使用本地缓存(常用于静态资源优化,减少带宽消耗)。
- 307 Temporary Redirect:与 302 类似,但要求客户端必须使用原请求方法(如 POST 不能改为 GET)。
4xx:客户端错误状态码(请求存在错误,服务器无法处理)
表示客户端请求有问题(如语法错误、权限不足),服务器无法处理。
- 400 Bad Request:请求语法错误(如参数格式错误),服务器无法解析。
- 401 Unauthorized:请求需要身份认证(如未登录时访问需登录的页面),通常会返回登录页面。
- 403 Forbidden:服务器拒绝请求(如登录后无权限访问某资源)。
- 404 Not Found:请求的资源不存在(最常见错误,如 URL 拼写错误)。
- 405 Method Not Allowed:请求方法不被允许(如用 POST 访问仅支持 GET 的接口)。
- 408 Request Timeout:客户端请求超时(服务器等待太久未收到完整请求)。
- 409 Conflict:请求与服务器当前状态冲突(如创建已存在的用户)。
5xx:服务器错误状态码(服务器处理请求时发生错误)
表示服务器在处理请求时出现内部错误。
- 500 Internal Server Error:服务器内部错误(如代码 bug、数据库连接失败)。
- 502 Bad Gateway:服务器作为网关 / 代理时,收到上游服务器的无效响应(如反向代理后端服务崩溃)。
- 503 Service Unavailable:服务器暂时无法处理请求(如维护中、负载过高),通常会包含重试时间。
- 504 Gateway Timeout:服务器作为网关 / 代理时,等待上游服务器响应超时。
总结
状态码的第一位数字代表大类:
- 1xx:临时响应
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务器错误
理解状态码有助于快速定位问题(如 4xx 排查客户端请求,5xx 排查服务器故障)。
状态码中204和206区别
204 和 206 的区别
| 状态码 | 含义 | 适用场景 | 是否返回内容 |
|---|---|---|---|
| 204 | 无内容 | 表单提交、删除操作等 | 否 |
| 206 | 部分内容 | 分片下载、断点续传等 | 是 |
HTTP 状态码是服务器对客户端请求的响应结果的一种标准化表示。204 和 206 是两个常见的 HTTP 状态码,分别表示不同的响应状态。以下是它们的详细解释:
204 No Content(无内容)
(1) 含义
- 204 表示服务器成功处理了请求,但不需要返回任何实体内容。
- 通常用于以下场景:请求成功,但客户端不需要更新当前页面。表单提交成功,但不需要跳转或刷新页面。
(2) 示例场景
- 表单提交: 用户提交表单后,服务器成功处理了数据,但不需要返回新页面。
- 删除操作: 删除资源成功后,服务器不需要返回被删除的资源内容。
(3) 响应示例
HTTP/1.1 204 No Content
Date: Mon, 23 Oct 2023 12:00:00 GMT
Server: Apache
206 Partial Content(部分内容)
(1) 含义
- 206 表示服务器成功处理了部分 GET 请求。
- 通常用于以下场景:客户端请求资源的某一部分(如分片下载或断点续传)。服务器返回请求范围内的内容。
(2) 示例场景
- 分片下载:客户端请求文件的某一部分(如视频文件的某个时间段)。
- 断点续传:客户端从上次中断的地方继续下载文件。
(3) 响应示例
HTTP/1.1 206 Partial Content
Date: Mon, 23 Oct 2023 12:00:00 GMT
Server: Apache
Content-Range: bytes 0-499/2000
Content-Length: 500
Content-Type: video/mp4
[文件的部分内容]
Content-Range:表示返回的内容范围(如bytes 0-499/2000表示返回前 500 字节,文件总大小为 2000 字节)。Content-Length:表示返回内容的长度。Content-Type:表示返回内容的类型。
总结
- 204 No Content:表示请求成功,但不需要返回内容。适用于不需要更新客户端页面的场景。
- 206 Partial Content:表示请求成功,但只返回部分内容。适用于分片下载或断点续传的场景。
参考:
HTTP状态码全解析:从100到599,这15个你必须掌握!
五、CDN
CDN(内容分发网络)通过一系列技术手段和策略实现优化,旨在提升内容分发效率、降低延迟、提高可用性和用户体验。
1、CDN优化的关键实现方式
1. 全球分布式节点部署
CDN 服务商在全球各地部署大量服务器节点(POP,Point of Presence),这些节点靠近用户地理位置。当用户请求内容时,CDN 会将请求导向最近的节点,减少数据传输距离,从而降低延迟。
- 智能 DNS 解析:根据用户 IP 地址,将请求路由到最近的 CDN 节点。
- Anycast 技术:多个节点使用相同 IP 地址,通过 BGP 协议选择最优路径。
2. 内容缓存策略
CDN 通过缓存静态内容(如图片、CSS、JS 文件、HTML 页面等)减少源站负载,并加速内容交付:
-
分层缓存
- 边缘节点缓存:最接近用户的节点,缓存热门内容。
- 区域节点缓存:负责服务多个边缘节点,缓存次热门内容。
- 源站:原始内容存储地,仅在缓存未命中时被访问。
-
缓存失效策略
- 时间驱动:设置 TTL(Time-To-Live),过期后重新从源站获取。
- 事件驱动:源站内容更新时主动推送刷新指令(Purge)到 CDN 节点。
3. 内容优化与压缩
CDN 会对内容进行预处理,提升传输效率:
-
文件压缩:对文本内容(如 HTML、CSS、JS)进行 gzip 或 Brotli 压缩,减少传输体积。
-
图片优化
- 自动转换为更高效的格式(如 WebP)。
- 按需调整图片质量和尺寸(如响应式图片)。
-
代码精简:去除不必要的空格、注释,缩短文件大小。
4. 动态内容加速
针对动态内容(如 API 请求、用户生成内容),CDN 采用特殊优化技术:
- 动态内容缓存:缓存部分动态内容的片段(如导航栏、广告位)。
- 协议优化:使用 HTTP/3(基于 QUIC)减少连接延迟,支持多路复用。
- 边缘计算:在 CDN 节点运行轻量级代码(如 JavaScript),处理部分动态逻辑,减少源站负载。
5. 负载均衡与高可用性
CDN 通过以下方式确保服务稳定和高可用:
- 多节点冗余:同一区域部署多个节点,避免单点故障。
- 实时监控与自动切换:监控节点健康状态,自动将流量导向正常节点。
- DDoS 防护:利用分布式节点分散攻击流量,过滤恶意请求。
6. 预取与预热
- 内容预取:预测用户可能访问的内容,提前缓存到边缘节点。
- 内容预热:在流量高峰前,主动将热门内容推送到各节点。
7. 协议与传输优化
- HTTP/2/3 支持:提升并发性能,减少头部阻塞。
- TCP 优化:调整 TCP 参数(如初始窗口大小),加速连接建立。
- QUIC 协议:基于 UDP,减少握手延迟,优化移动网络性能。
8. 安全增强
CDN 通常集成安全功能:
- HTTPS 支持:提供免费 SSL 证书,加密数据传输。
- WAF(Web 应用防火墙) :过滤 SQL 注入、XSS 等攻击。
- Bot 管理:识别并拦截恶意爬虫和自动化工具。
9. 数据分析与智能调度
- 实时流量分析:根据用户行为和流量模式调整缓存策略。
- A/B 测试:对比不同节点或配置的性能,优化路由策略。
10. 与源站集成
CDN 与源站紧密协作,确保内容一致性和高效同步:
- 回源优化:控制回源请求的频率和方式,避免源站过载。
- 推送机制:源站内容更新时,主动通知 CDN 节点刷新缓存。
示例:CDN 优化前后对比
| 指标 | 未使用 CDN | 使用 CDN 后 |
|---|---|---|
| 加载时间 | 5 秒 | 1.2 秒 |
| 全球可用性 | 98% | 99.9% |
| 源站负载 | 高 | 低 |
| 抗 DDoS 能力 | 弱 | 强 |
11.总结
CDN 通过分布式架构、缓存策略、内容优化、协议升级和安全增强等多种手段,全面提升内容分发效率和用户体验。选择合适的 CDN 服务商(如 Akamai、Cloudflare、阿里云 CDN 等)并结合业务需求配置优化参数,是实现高效 CDN 的关键。
2、CDN回源是什么?
CDN回源是指当用户请求的内容在CDN节点上未缓存或缓存已过期时,CDN节点会向源站服务器请求该资源的过程。这一机制确保了用户始终能获取到最新内容,同时减轻源站直接处理所有请求的压力。
回源的核心触发条件
回源主要发生在以下场景:
- 缓存未命中*:用户请求的资源首次到达CDN节点,节点尚未缓存该内容。
- 缓存过期*:资源在CDN节点上的缓存时间已结束,节点需重新从源站获取最新版本。
- 主动预热*:通过CDN管理后台或API主动触发回源,提前将资源加载到节点缓存中。
回源的工作流程
- 用户请求*:终端用户访问网站时,DNS解析将请求指向最近的CDN节点。
- 节点检查缓存:CDN节点首先检查本地是否缓存了请求的资源。
- 发起回源请求:若未命中缓存,节点根据配置的回源策略(如回源HOST、协议等)向源站发送请求。
- 资源返回与缓存:源站响应后,CDN节点将资源返回给用户,并缓存一份以备后续请求。
关键配置与优化
- 回源HOST:当源站托管多个域名时,指定CDN节点回源时访问的具体域名。
- 回源协议:配置CDN节点回源时使用的协议(如HTTP/HTTPS),需与源站兼容。
- Range回源*:支持分片请求,仅获取资源的部分内容,提升效率。
- 性能监控*:通过回源率(回源流量比或请求数比例)评估CDN效率,比值越低说明缓存命中率越高,性能越好。
回源是CDN保障内容新鲜度和可用性的核心机制,合理配置可显著降低源站负载并提升用户体验。
六、TCP
1、TCP三次握手和四次挥手
为什么要进行三次握手:为了确认对方的发送和接收能力。
三次握手
三次握手主要流程:
- 一开始双方处于 CLOSED 状态,然后服务端开始监听某个端口进入 LISTEN 状态
- 然后客户端主动发起连接,发送 SYN,然后自己变为 SYN-SENT,seq = x
- 服务端收到之后,返回 SYN seq = y 和 ACK ack = x + 1(对于客户端发来的 SYN),自己变成 SYN-REVD
- 之后客户端再次发送 ACK seq = x + 1, ack = y + 1给服务端,自己变成 EASTABLISHED 状态,服务端收到 ACK,也进入 ESTABLISHED
SYN 需要对端确认,所以 ACK 的序列化要加一,凡是需要对端确认的,一点要消耗 TCP 报文的序列化
为什么不是两次?
无法确认客户端的接收能力。
如果首先客户端发送了 SYN 报文,但是滞留在网络中,TCP 以为丢包了,然后重传,两次握手建立了连接。
等到客户端关闭连接了。但是之后这个包如果到达了服务端,那么服务端接收到了,然后发送相应的数据表,就建立了链接,但是此时客户端已经关闭连接了,所以带来了链接资源的浪费。
为什么不是四次?
四次以上都可以,只不过 三次就够了。
四次挥手
- 一开始都处于 ESTABLISH 状态,然后客户端发送 FIN 报文,带上 seq = p,状态变为 FIN-WAIT-1。
- 服务端收到之后,发送 ACK 确认,ack = p + 1,然后进入 CLOSE-WAIT 状态。
- 客户端收到之后进入 FIN-WAIT-2 状态。
- 过了一会等数据处理完,再次发送 FIN、ACK,seq = q,ack = p + 1,进入 LAST-ACK 阶段。
- 客户端收到 FIN 之后,客户端收到之后进入 TIME_WAIT(等待 2MSL),然后发送 ACK 给服务端 ack = 1 + 1。
- 服务端收到之后进入 CLOSED 状态。
客户端这个时候还需要等待两次 MSL 之后,如果没有收到服务端的重发请求,就表明 ACK 成功到达,挥手结束,客户端变为 CLOSED 状态,否则进行 ACK 重发。
为什么需要等待 2MSL(Maximum Segement Lifetime):
因为如果不等待的话,如果服务端还有很多数据包要给客户端发,且此时客户端端口被新应用占据,那么就会接收到无用的数据包,造成数据包混乱,所以说最保险的方法就是等服务器发来的数据包都死翘翘了再启动新应用。
- 1个 MSL 保证四次挥手中主动关闭方最后的 ACK 报文能最终到达对端。
- 1个 MSL 保证对端没有收到 ACK 那么进行重传的 FIN 报文能够到达。
为什么是四次而不是三次?
如果是三次的话,那么服务端的 ACK 和 FIN 合成一个挥手,那么长时间的延迟可能让 TCP 一位 FIN 没有达到服务器端,然后让客户的不断的重发 FIN。
2、TCP流量控制
对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。
而流量控制索要做的事情,就是在通过接收缓存区的大小,控制发送端的发送。如果对方的接收缓存区满了,就不能再继续发送了。
要具体理解流量控制,首先需要了解滑动窗口的概念。
TCP 滑动窗口
TCP 滑动窗口分为两种: 发送窗口和接收窗口。
发送窗口
发送端的滑动窗口结构如下:
其中包含四大部分:
- 已发送且已确认
- 已发送但未确认
- 未发送但可以发送
- 未发送也不可以发送
其中有一些重要的概念,我标注在图中:
发送窗口就是图中被框住的范围。SND 即send, WND 即window, UNA 即unacknowledged, 表示未被确认,NXT 即next, 表示下一个发送的位置。
接收窗口
接收端的窗口结构如下:
REV 即 receive,NXT 表示下一个接收的位置,WND 表示接收窗口大小。
流量控制过程
这里我们不用太复杂的例子,以一个最简单的来回来模拟一下流量控制的过程,方便大家理解。
首先双方三次握手,初始化各自的窗口大小,均为 200 个字节。
假如当前发送端给接收端发送 100 个字节,那么此时对于发送端而言,SND.NXT 当然要右移 100 个字节,也就是说当前的可用窗口减少了 100 个字节,这很好理解。
现在这 100 个到达了接收端,被放到接收端的缓冲队列中。不过此时由于大量负载的原因,接收端处理不了这么多字节,只能处理 40 个字节,剩下的 60 个字节被留在了缓冲队列中。
注意了,此时接收端的情况是处理能力不够用啦,你发送端给我少发点,所以此时接收端的接收窗口应该缩小,具体来说,缩小 60 个字节,由 200 个字节变成了 140 字节,因为缓冲队列还有 60 个字节没被应用拿走。
因此,接收端会在 ACK 的报文首部带上缩小后的滑动窗口 140 字节,发送端对应地调整发送窗口的大小为 140 个字节。
此时对于发送端而言,已经发送且确认的部分增加 40 字节,也就是 SND.UNA 右移 40 个字节,同时发送窗口缩小为 140 个字节。
这也就是流量控制的过程。尽管回合再多,整个控制的过程和原理是一样的。
3、TCP拥塞控制
上一节所说的流量控制发生在发送端跟接收端之间,并没有考虑到整个网络环境的影响,如果说当前网络特别差,特别容易丢包,那么发送端就应该注意一些了。而这,也正是拥塞控制需要处理的问题。
对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:
- 拥塞窗口(Congestion Window,cwnd)
- 慢启动阈值(Slow Start Threshold,ssthresh)
涉及到的算法有这几个:
- 慢启动
- 拥塞避免
- 快速重传和快速恢复
接下来,我们就来一一拆解这些状态和算法。首先,从拥塞窗口说起。
拥塞窗口
拥塞窗口(Congestion Window,cwnd)是指目前自己还能传输的数据量大小。
那么之前介绍了接收窗口的概念,两者有什么区别呢?
- 接收窗口(rwnd)是
接收端给的限制 - 拥塞窗口(cwnd)是
发送端的限制
限制谁呢?
限制的是发送窗口的大小。
有了这两个窗口,如何来计算发送窗口?
发送窗口大小 = min(rwnd, cwnd)
取两者的较小值。而拥塞控制,就是来控制cwnd的变化。
慢启动
刚开始进入传输数据的时候,你是不知道现在的网路到底是稳定还是拥堵的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络灾难。
因此,拥塞控制首先就是要采用一种保守的算法来慢慢地适应整个网路,这种算法叫慢启动。运作过程如下:
- 首先,三次握手,双方宣告自己的接收窗口大小
- 双方初始化自己的拥塞窗口(cwnd)大小
- 在开始传输的一段时间,发送端每收到一个 ACK,拥塞窗口大小加 1,也就是说,每经过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。
难道就这么无止境地翻倍下去?当然不可能。它的阈值叫做慢启动阈值,当 cwnd 到达这个阈值之后,好比踩了下刹车,别涨了那么快了,老铁,先 hold 住!
在到达阈值后,如何来控制 cwnd 的大小呢?
这就是拥塞避免做的事情了。
拥塞避免
原来每收到一个 ACK,cwnd 加1,现在到达阈值了,cwnd 只能加这么一点: 1 / cwnd。那你仔细算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最后拥塞窗口的大小 cwnd 总共才增加 1。
也就是说,以前一个 RTT 下来,cwnd翻倍,现在cwnd只是增加 1 而已。
当然,慢启动和拥塞避免是一起作用的,是一体的。
快速重传和快速恢复
快速重传
在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。
比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK。当发送端收到 3 个重复的 ACK 时,意识到丢包了,于是马上进行重传,不用等到一个 RTO 的时间到了才重传。
这就是快速重传,它解决的是是否需要重传的问题。
选择性重传
那你可能会问了,既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?
当然第 6、7 个都已经到达了,TCP 的设计者也不傻,已经传过去干嘛还要传?干脆记录一下哪些包到了,哪些没到,针对性地重传。
在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edge和right edge告知发送端已经收到了哪些区间的数据报。因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment) ,它解决的是如何重传的问题。
快速恢复
当然,发送端收到三次重复 ACK 之后,发现丢包,觉得现在的网络已经有些拥塞了,自己会进入快速恢复阶段。
在这个阶段,发送端如下改变:
- 拥塞阈值降低为 cwnd 的一半
- cwnd 的大小变为拥塞阈值
- cwnd 线性增加
以上就是 TCP 拥塞控制的经典算法: 慢启动、拥塞避免、快速重传和快速恢复。
4、一个TCP连接能发几个http请求?
HTTP1.1 协议
- 默认情况(持久连接) :在HTTP1.1中,默认使用持久连接(Keep-Alive),这意味着一个TCP连接可以复用于多个HTTP请求。具体来说,一个TCP连接可以发送多个HTTP请求,但这些请求是串行处理的,即在同一时刻只能处理一个请求,后续请求必须等待前一个请求的响应完成后才能开始。
- 连接管理:如果请求头中包含
Connection: close,则连接在完成一个请求后会立即关闭,此时只能发送一个HTTP请求。 - 请求流水线(Pipelining) :HTTP1.1支持请求流水线,允许客户端在不等待响应的情况下连续发送多个请求。然而,服务器必须按接收顺序返回响应,这可能导致队头阻塞问题。但实践中,浏览器通常默认关闭此功能。
HTTP2 协议
- 多路复用:HTTP/2引入了多路复用机制,允许在一个TCP连接上并发发送多个HTTP请求,并独立接收响应。这解决了HTTP/1.1的串行瓶颈问题,显著提升性能。
HTTP3 协议
- 基于UDP:HTTP/3使用QUIC协议(基于UDP),进一步优化了连接管理,避免了队头阻塞问题,支持更高效的并发请求。
实际应用中的注意事项
- 服务器配置:例如,在Nginx服务器中,需设置
keepalive_timeout大于0才能启用持久连接功能。 - 浏览器限制:尽管HTTP/1.1支持持久连接,但浏览器通常对单个域名的并发连接数有限制(如6个),以平衡性能和资源使用。
总结
- HTTP1.1:一个TCP连接可发送多个请求(通过持久连接),但默认串行执行;若使用流水线,可并行发送请求但响应顺序固定。
- HTTP2/HTTP3:一个TCP连接可高效并发发送多个请求,无串行限制。
5、TCP和UDP区别
TCP(传输控制协议)和 UDP(用户数据报协议)是 TCP/IP 协议栈中传输层的两个核心协议,它们在设计目标、工作方式和适用场景上有显著区别。
下面为你罗列了一些 TCP 和 UDP 的不同点,方便理解,方便记忆。
| TCP | UDP |
|---|---|
| TCP 是面向连接的协议 | UDP 是无连接的协议 |
| TCP 在发送数据前先需要建立连接,然后再发送数据 | UDP 无需建立连接就可以直接发送大量数据 |
| TCP 会按照特定顺序重新排列数据包 | UDP 数据包没有固定顺序,所有数据包都相互独立 |
| TCP 传输的速度比较慢 | UDP 的传输会更快 |
| TCP 的头部至少有 20 字节 | UDP 的头部只需要 8 个字节 |
| TCP 是重量级的。在发送任何用户数据之前,TCP需要三次握手建立连接 | UDP 是轻量级的。没有跟踪连接,消息排序等 |
| TCP 会进行错误校验,并能够进行错误恢复 | UDP 也会错误检查,但会丢弃错误的数据包 |
| TCP 有发送确认 | UDP 没有发送确认 |
| TCP 会使用握手协议,例如 SYN,SYN-ACK,ACK | 无握手协议 |
| TCP 是可靠的,因为它可以确保将数据传送到路由器 | 在 UDP 中不能保证将数据传送到目标 |
| 面向连接(三次握手建立连接) | 无连接(直接发送) |
| 可靠(确认、重传、排序等) | 不可靠(无确认,可能丢失) |
| 传输效率低(开销大、延迟高) | 传输效率高(开销小、延迟低) |
| 数据形式:字节流(无边界) | 数据形式:数据报(有边界) |
| 支持 拥塞控制 / 流量控制 | 不支持 拥塞控制 / 流量控制 |
| 对可靠性要求高的场景(文件、网页) | 对实时性要求高的场景(视频、游戏) |
一句话总结:TCP 像 “挂号信”,确保送达但流程繁琐;UDP 像 “普通平信”,简单高效但不保证送达,两者分别满足不同的网络通信需求。
6、TCP粘包是怎么回事,如何处理?
默认情况下, TCP 连接会启⽤延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样可以减少 IO 消耗提⾼性能.
如果是传输⽂件的话, 那么根本不⽤处理粘包的问题, 来⼀个包拼⼀个包就好了。但是如果是多条消息, 或者是别的⽤途的数据那么就需要处理粘包.
下面看⼀个例⼦, 连续调⽤两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下⼏种常⻅的情况: A. 先接收到 data1, 然后接收到 data2 . B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部. C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据. D. ⼀次性接收到了 data1 和 data2 的全部数据.
其中的 BCD 就是我们常⻅的粘包的情况. ⽽对于处理粘包的问题, 常⻅的解决⽅案有:
- 多次发送之前间隔⼀个等待时间:只需要等上⼀段时间再进⾏下⼀次 send 就好, 适⽤于交互频率特别低的场景. 缺点也很明显, 对于⽐较频繁的场景⽽⾔传输效率实在太低,不过⼏乎不⽤做什么处理.
- 关闭 Nagle 算法:关闭 Nagle 算法, 在 Node.js 中你可以通过 socket.setNoDelay() ⽅法来关闭 Nagle 算法, 让每⼀次 send 都不缓冲直接发送。该⽅法⽐较适⽤于每次发送的数据都⽐较⼤ (但不是⽂件那么⼤), 并且频率不是特别⾼的场景。如果是每次发送的数据量⽐较⼩, 并且频率特别⾼的, 关闭 Nagle 纯属⾃废武功。另外, 该⽅法不适⽤于⽹络较差的情况, 因为 Nagle 算法是在服务端进⾏的包合并情况, 但是如果短时间内客户端的⽹络情况不好, 或者应⽤层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从⽽粘包的情况。 (如果是在稳定的机房内部通信那么这个概率是⽐较⼩可以选择忽略的)
- 进⾏封包/拆包: 封包/拆包是⽬前业内常⻅的解决⽅案了。即给每个数据包在发送之前, 于其前/后放⼀些有特征的数据, 然后收到数据的时 候根据特征数据分割出来各个数据包。
7、为什么udp不会粘包?
-
TCP协议是⾯向流的协议,UDP是⾯向消息的协议。UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据
-
UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏区分处理了。传输协议把数据当作⼀条独⽴的消息在⽹上传输,接收端只能接收独⽴的消息。接收端⼀次只能接收发送端发出的⼀个数据包,如果⼀次接受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会丢失⼀部分数据,即使丢失,接受端也不会分两次去接收。
8、说说半连接队列和 SYN Flood 攻击的关系
三次握手前,服务端的状态从CLOSED变为LISTEN, 同时在内部创建了两个队列:半连接队列和全连接队列,即SYN队列和ACCEPT队列。
半连接队列
当客户端发送SYN到服务端,服务端收到以后回复ACK和SYN,状态由LISTEN变为SYN_RCVD,此时这个连接就被推入了SYN队列,也就是半连接队列。
全连接队列
当客户端返回ACK, 服务端接收后,三次握手完成。这个时候连接等待被具体的应用取走,在被取走之前,它会被推入另外一个 TCP 维护的队列,也就是全连接队列(Accept Queue) 。
SYN Flood 攻击原理
SYN Flood 属于典型的 DoS/DDoS 攻击。其攻击的原理很简单,就是用客户端在短时间内伪造大量不存在的 IP 地址,并向服务端疯狂发送SYN。对于服务端而言,会产生两个危险的后果:
- 处理大量的
SYN包并返回对应ACK, 势必有大量连接处于SYN_RCVD状态,从而占满整个半连接队列,无法处理正常的请求。 - 由于是不存在的 IP,服务端长时间收不到客户端的
ACK,会导致服务端不断重发数据,直到耗尽服务端的资源。
如何应对 SYN Flood 攻击?
- 增加 SYN 连接,也就是增加半连接队列的容量。
- 减少 SYN + ACK 重试次数,避免大量的超时重发。
- 利用 SYN Cookie 技术,在服务端接收到
SYN后不立即分配连接资源,而是根据这个SYN计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK的时候带上这个Cookie值,服务端验证 Cookie 合法之后才分配连接资源。
9、介绍一下 TCP 报文头部的字段
报文头部结构如下(单位为字节):
请大家牢记这张图!
源端口、目标端口
如何标识唯一标识一个连接?答案是 TCP 连接的四元组——源 IP、源端口、目标 IP 和目标端口。
那 TCP 报文怎么没有源 IP 和目标 IP 呢?这是因为在 IP 层就已经处理了 IP 。TCP 只需要记录两者的端口即可。
序列号
即Sequence number, 指的是本报文段第一个字节的序列号。
从图中可以看出,序列号是一个长为 4 个字节,也就是 32 位的无符号整数,表示范围为 0 ~ 2^32 - 1。如果到达最大值了后就循环到0。
序列号在 TCP 通信的过程中有两个作用:
- 在 SYN 报文中交换彼此的初始序列号。
- 保证数据包按正确的顺序组装。
ISN
即Initial Sequence Number(初始序列号),在三次握手的过程当中,双方会用过SYN报文来交换彼此的 ISN。
ISN 并不是一个固定的值,而是每 4 ms 加一,溢出则回到 0,这个算法使得猜测 ISN 变得很困难。那为什么要这么做?
如果 ISN 被攻击者预测到,要知道源 IP 和源端口号都是很容易伪造的,当攻击者猜测 ISN 之后,直接伪造一个 RST 后,就可以强制连接关闭的,这是非常危险的。
而动态增长的 ISN 大大提高了猜测 ISN 的难度。
确认号
即ACK(Acknowledgment number)。用来告知对方下一个期望接收的序列号,小于ACK的所有字节已经全部收到。
标记位
常见的标记位有SYN,ACK,FIN,RST,PSH。
SYN 和 ACK 已经在上文说过,后三个解释如下: FIN: 即 Finish,表示发送方准备断开连接。
RST:即 Reset,用来强制断开连接。
PSH: 即 Push, 告知对方这些数据包收到后应该马上交给上层的应用,不能缓存。
窗口大小
占用两个字节,也就是 16 位,但实际上是不够用的。因此 TCP 引入了窗口缩放的选项,作为窗口缩放的比例因子,这个比例因子的范围在 0 ~ 14,比例因子可以将窗口的值扩大为原来的 2 ^ n 次方。
校验和
占用两个字节,防止传输过程中数据包有损坏,如果遇到校验和有差错的报文,TCP 直接丢弃之,等待重传。
可选项
可选项的格式如下:
常用的可选项有以下几个:
- TimeStamp: TCP 时间戳,后面详细介绍。
- MSS: 指的是 TCP 允许的从对方接收的最大报文段。
- SACK: 选择确认选项。
- Window Scale: 窗口缩放选项。
10、说说 TCP 快速打开的原理(TFO)
第一节讲了 TCP 三次握手,可能有人会说,每次都三次握手好麻烦呀!能不能优化一点?
可以啊。今天来说说这个优化后的 TCP 握手流程,也就是 TCP 快速打开(TCP Fast Open, 即TFO)的原理。
优化的过程是这样的,还记得我们说 SYN Flood 攻击时提到的 SYN Cookie 吗?这个 Cookie 可不是浏览器的Cookie, 用它同样可以实现 TFO。
TFO 流程
首轮三次握手
首先客户端发送SYN给服务端,服务端接收到。
注意哦!现在服务端不是立刻回复 SYN + ACK,而是通过计算得到一个SYN Cookie, 将这个Cookie放到 TCP 报文的 Fast Open选项中,然后才给客户端返回。
客户端拿到这个 Cookie 的值缓存下来。后面正常完成三次握手。
首轮三次握手就是这样的流程。而后面的三次握手就不一样啦!
后面的三次握手
在后面的三次握手中,客户端会将之前缓存的 Cookie、SYN 和HTTP请求(是的,你没看错)发送给服务端,服务端验证了 Cookie 的合法性,如果不合法直接丢弃;如果是合法的,那么就正常返回SYN + ACK。
重点来了,现在服务端能向客户端发 HTTP 响应了!这是最显著的改变,三次握手还没建立,仅仅验证了 Cookie 的合法性,就可以返回 HTTP 响应了。
当然,客户端的ACK还得正常传过来,不然怎么叫三次握手嘛。
流程如下:
注意: 客户端最后握手的 ACK 不一定要等到服务端的 HTTP 响应到达才发送,两个过程没有任何关系。
TFO 的优势
TFO 的优势并不在与首轮三次握手,而在于后面的握手,在拿到客户端的 Cookie 并验证通过以后,可以直接返回 HTTP 响应,充分利用了1 个RTT(Round-Trip Time,往返时延)的时间提前进行数据传输,积累起来还是一个比较大的优势。
11、能不能说说TCP报文中时间戳的作用?
timestamp是 TCP 报文首部的一个可选项,一共占 10 个字节,格式如下:
kind(1 字节) + length(1 字节) + info(8 个字节)
其中 kind = 8, length = 10, info 有两部分构成: timestamp和timestamp echo,各占 4 个字节。
那么这些字段都是干嘛的呢?它们用来解决那些问题?
接下来我们就来一一梳理,TCP 的时间戳主要解决两大问题:
- 计算往返时延 RTT(Round-Trip Time)
- 防止序列号的回绕问题
计算往返时延 RTT
在没有时间戳的时候,计算 RTT 会遇到的问题如下图所示:
如果以第一次发包为开始时间的话,就会出现左图的问题,RTT 明显偏大,开始时间应该采用第二次的;
如果以第二次发包为开始时间的话,就会导致右图的问题,RTT 明显偏小,开始时间应该采用第一次发包的。
实际上无论开始时间以第一次发包还是第二次发包为准,都是不准确的。
那这个时候引入时间戳就很好的解决了这个问题。
比如现在 a 向 b 发送一个报文 s1,b 向 a 回复一个含 ACK 的报文 s2 那么:
- step 1: a 向 b 发送的时候,
timestamp中存放的内容就是 a 主机发送时的内核时刻ta1。 - step 2: b 向 a 回复 s2 报文的时候,
timestamp中存放的是 b 主机的时刻tb,timestamp echo字段为从 s1 报文中解析出来的 ta1。 - step 3: a 收到 b 的 s2 报文之后,此时 a 主机的内核时刻是 ta2, 而在 s2 报文中的 timestamp echo 选项中可以得到
ta1, 也就是 s2 对应的报文最初的发送时刻。然后直接采用 ta2 - ta1 就得到了 RTT 的值。
防止序列号回绕问题
现在我们来模拟一下这个问题。
序列号的范围其实是在0 ~ 2 ^ 32 - 1, 为了方便演示,我们缩小一下这个区间,假设范围是 0 ~ 4,那么到达 4 的时候会回到 0。
| 第几次发包 | 发送字节 | 对应序列号 | 状态 |
|---|---|---|---|
| 1 | 0 ~ 1 | 0 ~ 1 | 成功接收 |
| 2 | 1 ~ 2 | 1 ~ 2 | 滞留在网络中 |
| 3 | 2 ~ 3 | 2 ~ 3 | 成功接收 |
| 4 | 3 ~ 4 | 3 ~ 4 | 成功接收 |
| 5 | 4 ~ 5 | 0 ~ 1 | 成功接收,序列号从0开始 |
| 6 | 5 ~ 6 | 1 ~ 2 | ??? |
假设在第 6 次的时候,之前还滞留在网路中的包回来了,那么就有两个序列号为1 ~ 2的数据包了,怎么区分谁是谁呢?这个时候就产生了序列号回绕的问题。
那么用 timestamp 就能很好地解决这个问题,因为每次发包的时候都是将发包机器当时的内核时间记录在报文中,那么两次发包序列号即使相同,时间戳也不可能相同,这样就能够区分开两个数据包了。
12、TCP 的超时重传时间是如何计算的?
TCP 具有超时重传机制,即间隔一段时间没有等到数据包的回复时,重传这个数据包。
那么这个重传间隔是如何来计算的呢?
今天我们就来讨论一下这个问题。
这个重传间隔也叫做超时重传时间(Retransmission TimeOut, 简称RTO),它的计算跟上一节提到的 RTT 密切相关。这里我们将介绍两种主要的方法,一个是经典方法,一个是标准方法。
经典方法
经典方法引入了一个新的概念——SRTT(Smoothed round trip time,即平滑往返时间),没产生一次新的 RTT. 就根据一定的算法对 SRTT 进行更新,具体而言,计算方式如下(SRTT 初始值为0):
SRTT = (α * SRTT) + ((1 - α) * RTT)
其中,α 是平滑因子,建议值是0.8,范围是0.8 ~ 0.9。
拿到 SRTT,我们就可以计算 RTO 的值了:
RTO = min(ubound, max(lbound, β * SRTT))
β 是加权因子,一般为1.3 ~ 2.0, lbound 是下界,ubound 是上界。
其实这个算法过程还是很简单的,但是也存在一定的局限,就是在 RTT 稳定的地方表现还可以,而在 RTT 变化较大的地方就不行了,因为平滑因子 α 的范围是0.8 ~ 0.9, RTT 对于 RTO 的影响太小。
标准方法
为了解决经典方法对于 RTT 变化不敏感的问题,后面又引出了标准方法,也叫Jacobson / Karels 算法。
一共有三步。
第一步: 计算SRTT,公式如下:
SRTT = (1 - α) * SRTT + α * RTT
注意这个时候的 α跟经典方法中的α取值不一样了,建议值是1/8,也就是0.125。
第二步: 计算RTTVAR(round-trip time variation)这个中间变量。
RTTVAR = (1 - β) * RTTVAR + β * (|RTT - SRTT|)
β 建议值为 0.25。这个值是这个算法中出彩的地方,也就是说,它记录了最新的 RTT 与当前 SRTT 之间的差值,给我们在后续感知到 RTT 的变化提供了抓手。
第三步: 计算最终的RTO:
RTO = µ * SRTT + ∂ * RTTVAR
µ建议值取1, ∂建议值取4。
这个公式在 SRTT 的基础上加上了最新 RTT 与它的偏移,从而很好的感知了 RTT 的变化,这种算法下,RTO 与 RTT 变化的差值关系更加密切。
13、能不能说说 Nagle 算法和延迟确认?
Nagle 算法
试想一个场景,发送端不停地给接收端发很小的包,一次只发 1 个字节,那么发 1 千个字节需要发 1000 次。这种频繁的发送是存在问题的,不光是传输的时延消耗,发送和确认本身也是需要耗时的,频繁的发送接收带来了巨大的时延。
而避免小包的频繁发送,这就是 Nagle 算法要做的事情。
具体来说,Nagle 算法的规则如下:
-
当第一次发送数据时不用等待,就算是 1byte 的小包也立即发送
-
后面发送满足下面条件之一就可以发了:
- 数据包大小达到最大段大小(Max Segment Size, 即 MSS)
- 之前所有包的 ACK 都已接收到
延迟确认
试想这样一个场景,当我收到了发送端的一个包,然后在极短的时间内又接收到了第二个包,那我是一个个地回复,还是稍微等一下,把两个包的 ACK 合并后一起回复呢?
延迟确认(delayed ack)所做的事情,就是后者,稍稍延迟,然后合并 ACK,最后才回复给发送端。TCP 要求这个延迟的时延必须小于500ms,一般操作系统实现都不会超过200ms。
不过需要主要的是,有一些场景是不能延迟确认的,收到了就要马上回复:
- 接收到了大于一个 frame 的报文,且需要调整窗口大小
- TCP 处于 quickack 模式(通过
tcp_in_quickack_mode设置) - 发现了乱序包
两者一起使用会怎样?
前者意味着延迟发,后者意味着延迟接收,会造成更大的延迟,产生性能问题。
14、如何理解TCP的keep-alive?
大家都听说过 http 的keep-alive, 不过 TCP 层面也是有keep-alive机制,而且跟应用层不太一样。
试想一个场景,当有一方因为网络故障或者宕机导致连接失效,由于 TCP 并不是一个轮询的协议,在下一个数据包到达之前,对端对连接失效的情况是一无所知的。
这个时候就出现了 keep-alive, 它的作用就是探测对端的连接有没有失效。
在 Linux 下,可以这样查看相关的配置:
sudo sysctl -a | grep keepalive
// 每隔 7200 s 检测一次
net.ipv4.tcp_keepalive_time = 7200
// 一次最多重传 9 个包
net.ipv4.tcp_keepalive_probes = 9
// 每个包的间隔重传间隔 75 s
net.ipv4.tcp_keepalive_intvl = 75
不过,现状是大部分的应用并没有默认开启 TCP 的keep-alive选项,为什么?
站在应用的角度:
- 7200s 也就是两个小时检测一次,时间太长
- 时间再短一些,也难以体现其设计的初衷, 即检测长时间的死连接
因此是一个比较尴尬的设计。
七、UDP
八、DNS
DNS(Domain Name System,域名系统)是互联网的核心协议之一,被称为 “互联网的地址簿”,其核心作用是将人类易读的域名(如www.google.com)转换为计算机可识别的 IP 地址(如142.250.185.14) ,使客户端能通过域名访问服务器。
DNS的核心功能
- 域名解析:最主要功能,将域名映射到对应的 IP 地址(正向解析),也可将 IP 地址反向映射到域名(反向解析)。
- 负载均衡:通过返回多个 IP 地址,实现请求分发(如大型网站会为同一域名配置多个服务器 IP)。
- 容错机制:当某个 IP 对应的服务器故障时,可返回其他可用 IP,提高服务可用性。
DNS的域名结构(层级体系)
域名采用层级化结构,类似文件系统的目录树,从右到左层级升高:
- 根域名:最顶层,用
.表示(如www.google.com.,末尾的.常省略)。 - 顶级域名(TLD) :根域名下的第一层,如
.com(商业)、.org(组织)、.cn(国家)。 - 二级域名:顶级域名下的层级,如
google.com中的google。 - 子域名:二级域名下可继续细分,如
www.google.com中的www(常用作网站前缀)。
示例:mail.tech.example.com的层级为:
com(顶级)→ example(二级)→ tech(三级)→ mail(四级)
DNS解析流程(以解析www.google.com为例)
DNS 解析采用递归查询 + 迭代查询结合的方式,流程如下:
-
客户端查询本地缓存:
浏览器、操作系统会缓存近期解析过的域名 - IP 映射,若存在则直接返回(最快,无需网络请求)。 -
本地 DNS 服务器查询:
若本地缓存无结果,客户端向本地 DNS 服务器(如路由器分配的 DNS 或手动设置的8.8.8.8)发送查询请求。 -
迭代查询各级 DNS 服务器:
本地 DNS 服务器若自身无缓存,会逐级向更高层级的 DNS 服务器查询:- 先查询根 DNS 服务器,获取
.com顶级域名服务器的 IP。 - 再查询
.com顶级域名服务器,获取google.com权威 DNS 服务器的 IP。 - 最后查询
google.com的权威 DNS 服务器,获取www.google.com对应的 IP。
- 先查询根 DNS 服务器,获取
-
返回结果并缓存:
本地 DNS 服务器将获取的 IP 返回给客户端,同时缓存该结果(一段时间内再次查询可直接返回)。
DNS关键特点
- 分布式架构:全球有大量根服务器、顶级域名服务器和权威服务器,共同构成分布式系统,避免单点故障。
- 缓存机制:各级都有缓存(客户端、本地 DNS、服务器),减少重复查询,提升解析速度。
- UDP 协议为主:DNS 查询默认使用 UDP 的 53 端口(传输快),复杂查询(如返回数据量大)会自动切换到 TCP。
DNS 是互联网的 “翻译官”,解决了 “人类记域名、机器认 IP” 的矛盾。没有 DNS,用户访问网站需记住冗长的 IP 地址(如142.250.185.14),互联网的易用性会大幅下降。其分布式架构和缓存机制确保了高效、可靠的域名解析服务,是现代网络通信的基础。
DNS查询过程
这部分算是对DNS的补充。
NS(域名系统)的完整查询过程是一个从本地到全球分布式服务器的层级查询流程,结合了缓存机制和层级解析,目的是高效地将域名(如www.example.com)转换为 IP 地址。以下是详细步骤:
DNS层级结构
DNS 服务器按层级划分,从上到下依次为:
- 根 DNS 服务器(全球共 13 组,负责顶级域名的解析)
- 顶级域名服务器(如
.com、.cn、.org对应的服务器) - 权威 DNS 服务器(域名持有者配置的服务器,存储域名与 IP 的映射关系)
- 本地 DNS 服务器(用户设备或网络服务商提供的 DNS 服务器,如路由器 DNS、ISP DNS)
完整查询流程(以 “访问www.example.com” 为例)
1. 客户端本地缓存查询(最快路径)
-
当用户在浏览器输入
www.example.com并回车时,客户端会先检查本地缓存:- 浏览器缓存:浏览器会缓存近期解析过的域名(如 Chrome 默认缓存 1 分钟到 1 小时)。
- 操作系统缓存:操作系统(如 Windows 的
hosts文件、DNS 缓存服务)也会缓存域名解析结果。
-
若命中缓存:直接返回 IP 地址,无需后续步骤,解析结束。
2. 本地 DNS 服务器查询(递归查询)
-
若本地缓存无结果,客户端会向本地 DNS 服务器(通常由路由器或 ISP 自动分配,如
192.168.1.1或223.5.5.5)发送查询请求,这是一次递归查询(客户端只需等待最终结果,无需自己逐级查询)。 -
本地 DNS 服务器先检查自身缓存:
- 若命中缓存:直接将 IP 返回给客户端。
- 若未命中:进入下一步,向更高层级的 DNS 服务器发起迭代查询。
3. 向根 DNS 服务器查询(迭代查询第一步)
-
本地 DNS 服务器向根 DNS 服务器(全球共 13 组,如
a.root-servers.net)发送查询请求:- 根服务器不直接存储
www.example.com的 IP,但知道.com顶级域名服务器的地址。 - 根服务器返回:
.com顶级域名服务器的 IP 列表(如a.gtld-servers.net)。
- 根服务器不直接存储
4. 向顶级域名服务器查询(迭代查询第二步)
-
本地 DNS 服务器向
.com顶级域名服务器发送查询请求:- 顶级域名服务器知道
example.com对应的权威 DNS 服务器地址(由域名持有者在购买域名时配置,如阿里云 DNS、Cloudflare DNS)。 - 顶级域名服务器返回:
example.com的权威 DNS 服务器 IP 列表(如dns1.example.com)。
- 顶级域名服务器知道
5. 向权威 DNS 服务器查询(迭代查询第三步)
-
本地 DNS 服务器向
example.com的权威 DNS 服务器发送查询请求:- 权威 DNS 服务器是域名持有者直接管理的服务器,存储着
www.example.com与 IP 的映射关系(如192.0.2.1)。 - 权威 DNS 服务器返回:
www.example.com对应的 IP 地址。
- 权威 DNS 服务器是域名持有者直接管理的服务器,存储着
6. 结果返回与缓存
- 本地 DNS 服务器将获取到的 IP 地址返回给客户端,并将该结果缓存(缓存时间由域名的
TTL值决定,通常为几分钟到几小时)。 - 客户端收到 IP 地址后,缓存该结果,然后通过 IP 地址与目标服务器建立连接(如 TCP 连接),完成后续的 HTTP/HTTPS 请求。
7. 特殊情况
- DNS 负载均衡:权威 DNS 服务器可能返回多个 IP 地址(对应同一域名的不同服务器),本地 DNS 服务器或客户端会选择一个 IP 进行连接,实现流量分发。
- CDN 加速:若域名使用 CDN(如 Cloudflare、阿里云 CDN),权威 DNS 服务器会返回离客户端最近的 CDN 节点 IP,减少访问延迟。
- 反向 DNS 查询:少数场景下(如邮件服务器反垃圾邮件),会通过 IP 地址查询对应的域名(与正向解析流程相反)。
8. 核心逻辑
DNS 查询是一个 “从本地到全球,从缓存到权威” 的层级过程:
- 优先使用本地缓存(客户端→操作系统→本地 DNS),减少网络请求。
- 未命中缓存时,通过迭代查询从根服务器→顶级域名服务器→权威服务器逐级获取解析结果。
- 各级均有缓存机制,确保解析效率和可靠性。
这一分布式架构既解决了 “域名到 IP 的映射” 问题,又通过缓存和层级设计支撑了全球互联网的高效运转。
DNS 为什么使用 UDP 协议作为传输层协议?
「DNS 使用 UDP 协议作为传输层协议的主要原因是为了避免使用 TCP 协议时造成的连接时延。」
- 为了得到一个域名的 IP 地址,往往会向多个域名服务器查询,如果使用 TCP 协议,那么每次请求都会存在连接时延,这样使 DNS 服务变得很慢。
- 大多数的地址查询请求,都是浏览器请求页面时发出的,这样会造成网页的等待时间过长。
九、WebSocket
1、短轮询、长轮询和 WebSocket 间的区别?
短轮询
短轮询的基本思路:
- 浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行 响应。
- 这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。
优缺点👇
- 优点是比较简单,易于理解。
- 缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。
长轮询
长轮询的基本思路:
- 首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将 这个请求挂起,然后判断服务器端数据是否有更新。
- 如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
优缺点👇
- 长轮询和短轮询比起来,它的优点是
「明显减少了很多不必要的 http 请求次数」,相比之下节约了资源。 - 长轮询的缺点在于,连接挂起也会导致资源的浪费。
WebSocket
-
WebSocket 是 Html5 定义的一个新协议,与传统的 http 协议不同,该协议允许由服务器主动的向客户端推送信息。
-
使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息。
2、WebSocket与Ajax的区别
Ajax 即异步 JavaScript 和 XML,是一种创建交互式网页的应用的网页开发技术。
websocket 是 HTML5 的一种新协议,实现了浏览器和服务器的实时通信。
生命周期不同:
- websocket 是长连接,会话一直保持。
- ajax 发送接收之后就会断开。
适用范围:
- websocket 用于前后端实时交互数据。
- ajax 非实时。
发起人:
- AJAX 客户端发起。
- WebSocket 服务器端和客户端相互推送。