HTTP
HTTP
建立在TCP上进行可靠传输,规定了万维网客户程序和服务器程序交互方式的应用层协议,端口号为80
HTTP报文

HTTP的两类报文:
请求报文
请求行(Request Line): 包含请求方法、请求的URI(Uniform Resource Identifier)和HTTP协议的版本。
1 |
|
例如
1 |
|
请求头部(Request Headers): 包含了一系列关于请求的信息,如主机名、用户代理、接受的数据类型等。
1 |
|
例如
1 |
|
空行(Empty Line): 请求头部和消息体之间必须有一个空行。
请求消息体(Request Body): 对于一些请求,如POST请求,请求消息体包含需要传输给服务器的数据。
1 |
|
例如
1 |
|
响应报文
状态行(Status Line): 包含了HTTP协议的版本、一个状态码和状态消息。
1 |
|
例如:
1 |
|
响应头部(Response Headers): 包含了一系列关于响应的信息,如服务器信息、日期、内容类型等。
1 |
|
例如
1 |
|
空行(Empty Line): 响应头部和消息体之间必须有一个空行。
响应消息体(Response Body): 包含了从服务器返回的实际数据。
1 |
|
例如:
1 |
|
HTTP 状态码有哪些?
HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被成功处理。

1xx
类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
2xx
类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
- 「200 OK」是最常见的成功状态码,表示一切正常。如果是非
HEAD
请求,服务器返回的响应头都会有 body 数据。 - 「204 No Content」也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
- 「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
3xx
类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
- 「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
- 「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段 Location
,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
- 「304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。
4xx
类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
- 「400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
- 「403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
- 「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。
5xx
类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
- 「500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
- 「501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
- 「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
- 「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。
从输入 URL 到页面展示到底发生了什么?
总体来说分为以下几个步骤:
- 在浏览器中输入指定网页的 URL。
- 浏览器通过 DNS 协议,获取域名对应的 IP 地址。
- 浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
- 浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
- 服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器。
- 浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
- 浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。

所以图中的长长的 URL 实际上是请求服务器里的文件资源。当没有路径名时,就代表访问根目录下事先设置的默认文件,也就是 /index.html
或者 /default.html
这些文件,这样就不会发生混乱了。
HTTP/1.0 和 HTTP/1.1 有什么区别?
连接方式 : HTTP/1.0 为短连接,HTTP/1.1 支持长连接。
短连接:也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 TCP 连接,这样就会导致有大量的“握手报文”和“挥手报文”占用了带宽。
长连接:当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
HTTP/1.0 仍提供了长连接选项,即在请求头中加入Connection: Keep-alive
。同样的,在 HTTP/1.1 中,如果不希望使用长连接选项,也可以在请求头中加入Connection: close

队头阻塞:支持同时打开多个 TCP 连接,采用 Pipeline 请求方式,即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应。如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。

状态响应码 : HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。比如说,100 (Continue)
——在请求大资源前的预热请求,206 (Partial Content)
——范围请求的标识码,409 (Conflict)
——请求与当前资源的规定冲突,410 (Gone)
——资源已被永久转移,而且没有任何已知的转发地址。
缓存机制 : 在 HTTP/1.0 中主要使用 Header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP/1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
带宽(Range Header):HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP/1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
Host 头(Host Header)处理 :HTTP/1.1 引入了 Host 头字段,允许在同一 IP 地址上托管多个域名,从而支持虚拟主机的功能。而 HTTP/1.0 没有 Host 头字段,无法实现虚拟主机。
HTTP/1.1 和 HTTP/2.0 有什么区别?
- 多路复用(Multiplexing):HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本),互不干扰。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接(即下一个请求必须等待上一个响应),而浏览器为了控制资源会有 6-8 个 TCP 连接都限制。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能。HTTP/2针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。
- 服务器推送(Server Push):HTTP/2.0 支持服务器推送,可以在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。

- 二进制帧(Binary Frames):HTTP/2.0 使用二进制帧进行数据传输,而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效,减少了传输的数据量和带宽消耗。

- 头部压缩(Header Compression):HTTP/1.1 支持
Body
压缩,Header
不支持压缩。HTTP/2.0 支持对Header
压缩,如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。使用了专门为Header
压缩而设计的 HPACK 算法,减少了网络开销。这就是所谓的HPACK
算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
HTTP/2.0 和 HTTP/3.0 有什么区别?
传输协议:HTTP/2.0 是基于 TCP 协议实现的,HTTP/3.0 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。HTTP/3.0 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
连接建立:HTTP/2.0 需要经过经典的 TCP 三次握手过程(由于安全的 HTTPS 连接建立还需要 TLS 握手,共需要大约 3 个 RTT)。由于 QUIC 协议的特性(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。

- 队头阻塞:HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)
HTTP/2 虽然解决了 HTTP/1 协议中的 队列头部请求阻塞 (head of line blocking) 问题,但是 TCP 协议也存在类似的问题: TCP 在传输时使用序列号标识数据的顺序,一旦某个数据丢失,后面的数据需要等待这个数据重传后才能进行下一步处理
- 根据测试表明,在较差的网络环境中 (丢包率 >= 2%),HTTP/2 的性能甚至不如 HTTP/1, 因为 HTTP/1 一般会打开多个 TCP 连接,即使其中一个或多个连接出现丢包,剩下的连接依然可以进行数据传输

错误恢复:HTTP/3.0 具有更好的错误恢复机制,当出现丢包、延迟等网络问题时,可以更快地进行恢复和重传。而 HTTP/2.0 则需要依赖于 TCP 的错误恢复机制。
安全性:HTTP/2.0 和 HTTP/3.0 在安全性上都有较高的要求,支持加密通信,但在实现上有所不同。HTTP/2.0 使用 TLS 协议进行加密,而 HTTP/3.0 基于 QUIC 协议,包含了内置的加密和身份验证机制,可以提供更强的安全性。
连接迁移: 当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
HTTP 和 HTTPS 有什么区别?
- 端口号:HTTP 默认是 80,HTTPS 默认是 443。
- URL 前缀:HTTP 的 URL 前缀是
http://
,HTTPS 的 URL 前缀是https://
。 - 安全性和资源消耗:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
- SEO(搜索引擎优化):搜索引擎通常会更青睐使用 HTTPS 协议的网站,因为 HTTPS 能够提供更高的安全性和用户隐私保护。使用 HTTPS 协议的网站在搜索结果中可能会被优先显示,从而对 SEO 产生影响。
SSL/TLS协议
SSL(Secure Sockets Layer)协议是一种用于保护网络通信安全性的协议。它最初由网景公司设计,后来发展为TLS(Transport Layer Security)协议,TLS是SSL的继任者。SSL和TLS协议的目标是在客户端和服务器之间提供安全的通信通道,以防止敏感数据在传输过程中被窃取或篡改。
TLS加密原理
在传输层中,TCP通过三次握手建立了两端的连接。在已建立TCP连接的基础上,TLS也是通过几次握手来保证应用层的数据安全传输。在详细描述TLS的握手协议之前,我先简要描述下TLS是怎么保证数据安全的。
我们知道,要保证数据的安全,就要对两端的传输的报文进行加密,且要防止被破解。而在密码技术领域,一般有对称加密和非对称加密两种,对称加密算法是加密和解密都用的是同一个秘钥。而非对称加密有公钥和私钥两个秘钥,通过私钥加密的内容,可通过公钥和私钥解密。那应该用哪一种加密算法呢?
TLS的认证原理可以简略地用下图来表示:

用服务端公钥加密的随机数3只有服务器自己的私钥可以解密,因此该密钥是非对称密钥,而传输的数据是使用3个随机数通过DH算法组成的对称密钥,这种方式不仅提高了数据加密的效率,也提高的数据传输的安全性
什么是DH算法?
DH算法,即Diffie-Hellman算法,是一种密钥交换协议,由Whitfield Diffie和Martin Hellman在1976年提出。这种算法主要用于在非安全网络下通信双方密钥的安全建立,使得通信双方能够使用这个密钥进行消息的加密解密,从而实现通信的安全。
H算法的基本步骤如下:
- 选择一个公共的素数p和一个公共的基数g,g是p的一个原根。
- 通信双方各自选择一个私钥,Alice选择私钥a,Bob选择私钥b,并保持这些私钥的秘密。
- Alice计算A = g^a mod p并将其发送给Bob;Bob计算B = g^b mod p并将其发送给Alice。
- Alice和Bob使用对方的公钥和自己的私钥计算共享密钥:Alice计算K = B^a mod p,Bob计算K = A^b mod p。
- 最终,Alice和Bob得到了相同的共享密钥K,可以用于后续的加密通信。
可以只用对称加密来保证数据安全吗?
答案是不行的。原因有几个方面:
- 一个服务端站点是可以被世界上每一台主机访问的,每个浏览器客户端访问时就建立了一个数据连接通道,而每个连接就要有不同的对称秘钥。如果世界上所有连接都用同一个秘钥,那就没有安全可言了;
- 每个不同的TCP连接都要有不同的对称秘钥,那秘钥要怎么协商出来呢,这就是个问题,如果秘钥通过明文传输,那秘钥还是可以被第三方截取,然后用截取的秘钥解密传输的数据,因此还是不安全的;
可以只通过非对称加密来进行数据传输吗?
这样也是不可以的。首先,非对称加密的数据加解密相当耗时,通过这种加密方式传输数据会影响效率,这一点就已经被pass了。其次,通过这种方式也不能保证安全。
那同时使用对称加密+非对称加密方式加密?
TLS就是通过这种方式进行加密的。两者结合后,使用非对称加密的方式应该尽量降低,才能保证传输的效率。因此,TLS的规则就是:服务端生成非对称秘钥对,私钥自己保存,将公钥明文传输给客户端;客户端生成一个对称秘钥,再将对称秘钥使用收到的公钥进行加密,将加密后的秘钥传送给服务端;这样双端都持有相同的对称秘钥,之后的数据就通过该秘钥进行加密再传输。
但这样还有个问题,服务端的公钥如果通过明文传输,还是不安全,传输的数据依然可以被破解。那TLS怎么保证服务端传输的公钥不被调包呢?这时就用到了数字证书和CA(Certificate Authority)证书颁发机构了。
数字证书和CA的关系
数字证书是一个包含了某个服务站点名字、公钥的文件,该文件由CA颁发,能够证明服务站点的真实性。就好像每家公司都必须会在工商局办理一个营业执照,用于证明自己公司的真实身份一样。服务端通过发送数字证书给客户端,由客户端验证向CA验证证书的真实性(数字证书有一个数字签名,该数字签名通过CA的私钥进行加密,客户端使用CA的公钥就可以验证该证书的真实性),如果验证通过才取出证书中的公钥进行下一步的处理。此外,全球有无数多的网站,伴随着也会有很多层级的CA机构,就像工商局也分省级、市级的,CA的认证是逐级链式认证的过程。这点在后面会详细描述。
Cookie 和 Session 有什么区别?
Cookie
存储位置: Cookie是在客户端(用户浏览器)存储的小型文本文件,由浏览器保存。每次请求都会将相应的Cookie信息发送到服务器。
大小限制: 单个Cookie的大小通常有限制,一般在几KB左右。浏览器对每个域名下的Cookie数量也有限制。
安全性: Cookie中的数据在客户端是可见的,因此对于敏感信息的存储需要进行加密等安全处理。可以通过设置Cookie的属性来增加安全性,如将Cookie标记为HttpOnly,使其无法通过JavaScript脚本访问。
跨域: Cookie具有同源策略,只有在设置Cookie的域名和请求的域名一致时,Cookie才会被发送。
过期时间: 可以通过设置Cookie的过期时间来控制Cookie的有效期。
Session
存储位置: Session是在服务器端存储的用户会话信息。通常,会话数据存储在服务器的内存中或持久化到数据库中。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session
之后就可以标识这个用户并且跟踪这个用户了。
大小限制: 理论上,服务器端的存储空间是有限制的,Session可以存储更大量的数据,在高并发的环境下比较占用服务器资源
安全性: 由于Session存储在服务器端,因此对于客户端来说,Session数据是不可见的。这提供了更高的安全性,因为用户无法直接修改Session数据。
跨域: Session数据存储在服务器端,不受同源策略的限制。可以在不同域名之间共享会话信息。
过期时间: 通常,Session有一个过期时间,一旦超过这个时间,服务器会自动销毁该Session。

缺点
对跨域的限制
扩展性比较差
URI 和 URL 的区别是什么?
- URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
- URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源
URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息
GET 和 POST 的区别
GET 和 POST 是 HTTP 协议中两种常用的请求方法,它们在不同的场景和目的下有不同的特点和用法。一般来说,可以从以下几个方面来区分二者(重点搞清两者在语义上的区别即可):
语义(主要区别):GET 通常用于获取或查询资源,而 POST 通常用于创建或修改资源
幂等:GET 请求是幂等的,即多次重复执行不会改变资源的状态,而 POST 请求是不幂等的,即每次执行可能会产生不同的结果或影响资源的状态
格式:GET 请求的参数通常放在 URL 中,形成查询字符串(querystring),而 POST 请求的参数通常放在请求体(body)中,可以有多种编码格式,如 application/x-www-form-urlencoded、multipart/form-data、application/json 等。GET 请求的 URL 长度受到浏览器和服务器的限制,而 POST 请求的 body 大小则没有明确的限制。不过,实际上 GET 请求也可以用 body 传输数据,只是并不推荐这样做,因为这样可能会导致一些兼容性或者语义上的问题
缓存:由于 GET 请求是幂等的,它可以被浏览器或其他中间节点(如代理、网关)缓存起来,以提高性能和效率。而 POST 请求则不适合被缓存,因为它可能有副作用,每次执行可能需要实时的响应
安全性:GET 请求和 POST 请求如果使用 HTTP 协议的话,那都不安全,因为 HTTP 协议本身是明文传输的,必须使用 HTTPS 协议来加密传输数据。另外,GET 请求相比 POST 请求更容易泄露敏感数据,因为 GET 请求的参数通常放在 URL 中
再次提示,重点搞清两者在语义上的区别即可,实际使用过程中,也是通过语义来区分使用 GET 还是 POST。不过,也有一些项目所有的请求都用 POST,这个并不是固定的,项目组达成共识即可。
GET 请求可以带 body 吗?
RFC 规范并没有规定 GET 请求不能带 body 的。理论上,任何请求都可以带 body 的。只是因为 RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到 body。
另外,URL 中的查询参数也不是 GET 所独有的,POST 请求的 URL 中也可以有参数的。
什么是幂等和不幂等?
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
HTTP 缓存技术
HTTP 缓存有哪些实现方式?
对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,这样的话 HTTP/1.1 的性能肯定肉眼可见的提升。
所以,避免发送 HTTP 请求的方法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。
什么是强制缓存?
强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
如下图中,返回的是 200 状态码,但在 size 项中标识的是 from disk cache,就是使用了强制缓存。

强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
Cache-Control
, 是一个相对时间;Expires
,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
- 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
什么是协商缓存?
当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存

第一种:请求头部中的 If-Modified-Since
字段与响应头部中的 Last-Modified
字段实现,这两个字段的意思是:
- 响应头部中的
Last-Modified
:标示这个响应资源的最后修改时间; - 请求头部中的
If-Modified-Since
:告诉服务器指定时间,用于请求是与Last-Modified的时间对比。客户端发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。
第二种:请求头部中的 If-None-Match
字段与响应头部中的 ETag
字段,这两个字段的意思是:
- 响应头部中
Etag
:唯一标识响应资源; - 请求头部中的
If-None-Match
:浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。
第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。
如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有 Etag 和 Last-Modified 字段,那么客户端再下一次请求的时候,如果带上了 ETag 和 Last-Modified 字段信息给服务端,这时 Etag 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 有变化就不用在判断 Last-Modified 了,如果 Etag 没有变化,然后再看 Last-Modified。
为什么 ETag 的优先级更高?
这是因为 ETag 主要能解决 Last-Modified 几个比较难以解决的问题:
- 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
- 可能有些文件是在秒级以内修改的,
If-Modified-Since
能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次; - 有些服务器不能精确获取文件的最后修改时间。
注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
如何减少 HTTP 请求次数?
减少 HTTP 请求次数自然也就提升了 HTTP 性能,可以从这 3 个方面入手:
- 减少重定向请求次数;
- 合并请求;
- 延迟发送请求;
减少重定向请求次数
我们先来看看什么是重定向请求?
服务器上的一个资源可能由于迁移、维护等原因从 url1 移至 url2 后,而客户端不知情,它还是继续请求 url1,这时服务器不能粗暴地返回错误,而是通过 302
响应码和 Location
头部,告诉客户端该资源已经迁移至 url2 了,于是客户端需要再发送 url2 请求以获得服务器的资源。
那么,如果重定向请求越多,那么客户端就要多次发起 HTTP 请求,每一次的 HTTP 请求都得经过网络,这无疑会越降低网络性能。
另外,服务端这一方往往不只有一台服务器,比如源服务器上一级是代理服务器,然后代理服务器才与客户端通信,这时客户端重定向就会导致客户端与代理服务器之间需要 2 次消息传递,如下图:

如果重定向的工作交由代理服务器完成,就能减少 HTTP 请求次数了

除了 302
重定向响应码,还有其他一些重定向的响应码,你可以从下图看到:

其中,301
和 308
响应码是告诉客户端可以将重定向响应缓存到本地磁盘,之后客户端就自动用 url2 替代 url1 访问服务器的资源。
合并请求
如果把多个访问小文件的请求合并成一个大的请求,虽然传输的总资源还是一样,但是减少请求,也就意味着减少了重复发送的 HTTP 头部。
另外由于 HTTP/1.1 是请求响应模型,如果第一个发送的请求,未收到对应的响应,那么后续的请求就不会发送(PS:HTTP/1.1 管道模式是默认不使用的,所以讨论 HTTP/1.1 的队头阻塞问题,是不考虑管道模式的),于是为了防止单个请求的阻塞,所以一般浏览器会同时发起 5-6 个请求,每一个请求都是不同的 TCP 连接,那么如果合并了请求,也就会减少 TCP 连接的数量,因而省去了 TCP 握手和慢启动过程耗费的时间。

这种方式就是通过将多个小图片合并成一个大图片来减少 HTTP 请求的次数,以减少 HTTP 请求的次数,从而减少网络的开销。
除了将小图片合并成大图片的方式,还有服务端使用 webpack
等打包工具将 js、css 等资源合并打包成大文件,也是能达到类似的效果。
另外,还可以将图片的二进制数据用 base64
编码后,以 URL 的形式嵌入到 HTML 文件,跟随 HTML 文件一并发送
1 |
|
这样客户端收到 HTML 后,就可以直接解码出数据,然后直接显示图片,就不用再发起图片相关的请求,这样便减少了请求的次数。
可以看到,合并请求的方式就是合并资源,以一个大资源的请求替换多个小资源的请求。
但是这样的合并请求会带来新的问题,当大资源中的某一个小资源发生变化后,客户端必须重新下载整个完整的大资源文件,这显然带来了额外的网络消耗。
延迟发送请求
不要一口气吃成大胖子,一般 HTML 里会含有很多 HTTP 的 URL,当前不需要的资源,我们没必要也获取过来,于是可以通过「按需获取」的方式,来减少第一时间的 HTTP 请求次数。
请求网页的时候,没必要把全部资源都获取到,而是只获取当前用户所看到的页面资源,当用户向下滑动页面的时候,再向服务器获取接下来的资源,这样就达到了延迟发送请求的效果。
如何减少 HTTP 响应的数据大小?
对于 HTTP 的请求和响应,通常 HTTP 的响应的数据大小会比较大,也就是服务器返回的资源会比较大。
于是,我们可以考虑对响应的资源进行压缩,这样就可以减少响应的数据大小,从而提高网络传输的效率。
压缩的方式一般分为 2 种,分别是:
- 无损压缩
- 有损压缩
无损压缩
无损压缩是指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合用在文本文件、程序可执行文件、程序源代码。
首先,我们针对代码的语法规则进行压缩,因为通常代码文件都有很多换行符或者空格,这些是为了帮助程序员更好的阅读,但是机器执行时并不要这些符,把这些多余的符号给去除掉。
接下来,就是无损压缩了,需要对原始资源建立统计模型,利用这个统计模型,将常出现的数据用较短的二进制比特序列表示,将不常出现的数据用较长的二进制比特序列表示,生成二进制比特序列一般是「霍夫曼编码」算法。
gzip 就是比较常见的无损压缩。客户端支持的压缩算法,会在 HTTP 请求中通过头部中的 Accept-Encoding
字段告诉服务器:
1 |
|
服务器收到后,会从中选择一个服务器支持的或者合适的压缩算法,然后使用此压缩算法对响应资源进行压缩,最后通过响应头部中的 Content-Encoding
字段告诉客户端该资源使用的压缩算法。
1 |
|
gzip 的压缩效率相比 Google 推出的 Brotli 算法还是差点意思,也就是上文中的 br,所以如果可以,服务器应该选择压缩效率更高的 br 压缩算法。
有损压缩
与无损压缩相对的就是有损压缩,经过此方法压缩,解压的数据会与原始数据不同但是非常接近。
有损压缩主要将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比,这种方法经常用于压缩多媒体数据,比如音频、视频、图片。
可以通过 HTTP 请求头部中的 Accept
字段里的「 q 质量因子」,告诉服务器期望的资源质量。
1 |
|
关于图片的压缩,目前压缩比较高的是 Google 推出的 WebP 格式,它与常见的 Png 格式图片的压缩比例对比如下图:

可以发现,相同图片质量下,WebP 格式的图片大小都比 Png 格式的图片小,所以对于大量图片的网站,可以考虑使用 WebP 格式的图片,这将大幅度提升网络传输的性能。
关于音视频的压缩,音视频主要是动态的,每个帧都有时序的关系,通常时间连续的帧之间的变化是很小的。
比如,一个在看书的视频,画面通常只有人物的手和书桌上的书是会有变化的,而其他地方通常都是静态的,于是只需要在一个静态的关键帧,使用增量数据来表达后续的帧,这样便减少了很多数据,提高了网络传输的性能。对于视频常见的编码格式有 H264、H265 等,音频常见的编码格式有 AAC、AC3。
PING 命令的工作原理是什么?
PING 基于网络层的 ICMP(Internet Control Message Protocol,互联网控制报文协议),其主要原理就是通过在网络上发送和接收 ICMP 报文实现的。
ICMP 报文中包含了类型字段,用于标识 ICMP 报文类型。ICMP 报文的类型有很多种,但大致可以分为两类:
- 查询报文类型:向目标主机发送请求并期望得到响应。
- 差错报文类型:向源主机发送错误信息,用于报告网络中的错误情况。
PING 用到的 ICMP Echo Request(类型为 8 ) 和 ICMP Echo Reply(类型为 0) 属于查询报文类型 。
- PING 命令会向目标主机发送 ICMP Echo Request。
- 如果两个主机的连通性正常,目标主机会返回一个对应的 ICMP Echo Reply
127.0.0.1 和 localhost 以及 0.0.0.0 有区别吗
127.0.0.1
是回环地址。localhost
是域名,但默认等于127.0.0.1
。ping
回环地址和ping
本机地址,是一样的,走的是lo0 “假网卡”,都会经过网络层和数据链路层等逻辑,最后在快要出网卡前狠狠拐了个弯, 将数据插入到一个链表后就软中断通知 ksoftirqd 来进行收数据的逻辑,压根就不出网络。所以断网了也能ping
通回环地址。- 如果服务器
listen
的是0.0.0.0
,那么此时用127.0.0.1
和本机地址都可以访问到服务。