八股文
计算机网络
HTTP 相关
HTTP 定义
HTTP 是一个在计算机中「两点」之间「传输」文字/图片/音频等「超文本」的「约定和规范」
常见的 HTTP 状态码有哪些?
- 1×× : 请求处理中,请求已被接受,正在处理
- 属于提示类信息,是一种中间状态,实际中用的比较少
- 100-Continue:继续
- 2×× : 请求成功,请求被成功处理 200 OK
- 200-OK:成功。如果是非 HEAD 请求,响应头会有 body 数据
- 202-Accepted
- 已接受。已经接受请求,但未处理完成
- 204-No Content:与 200 基本相同,但响应没有 body 数据
- 206-Partial Content:应用于 HTTP 断点续传,返回的 body 是资源的一部分
- 3×× : 重定向,要完成请求必须进行进一步处理
- 301-Moved Permanently:永久重定向
- 302-Found:临时重定向
- 301 和 302 都会在响应头中添加字段 Location 表明 URL
- 304-Not Modified:已缓存(资源没修改,用之前缓存就行)
- 4×× : 客户端错误,请求报文不合法
- 400-Bad Request:请求有语法问题
- 401-Unauthorized:缺乏身份验证
- 403-Forbidden:服务器禁止访问资源
- 这个状态类似于
401
,但进入403
状态后即使重新验证也不会改变该状态。该访问是长期禁止的,并且与应用逻辑密切相关
- 这个状态类似于
- 404-Not Found:请求的资源在服务器上找不到
- 5×× : 服务器端错误,服务器不能处理合法请求
- 500-Internal Server Error:发生错误(比较笼统)
- 501-Not Implemented:该功能还不支持
- 502-Bad Gateway:服务器作为网关/代理返回,表示服务器自身工作正常,但访问后端服务器发送错误
- 503-Service Unavailable:当前忙,暂时无法响应
- 此响应代码表明 HTTP 服务器正常,只是下层 web 服务不能正常工作
- 最可能的理由是资源不足:服务器突然收到太多请求,以至于无法全部处理
- 由于此问题多半由客户端反复发送请求造成,因此 HTTP 服务器可以选择拒绝 客户端请求并返回 503
- 重试
- 服务器可以在响应头的 Retry-After 字段告知客户端何时可以重试
- 504-Gateway Timeout:网关超时
- 502 和 504 的区别
- 从网络角度,502 已经与后端建立了连接,但超时(电话打通了,但没人接);
- 504 与后端连接未建立,超时(电话没打通)。
- 502 和 504 的区别
http 中常见的 header 字段有哪些?
- 通用首部字段
- 请求/响应报文都会使用的首部
- Cache-Control 字段
- 控制缓存的工作机制
- no-cache/max-age=.../public/private
- Connection 字段
- 控制不再转发给代理的首部字段
- 管理持久连接
- 控制网络连接在当前会话完成后是否仍然保持打开状态。如果发送的值是
keep-alive
,则连接是持久的,不会关闭,允许对同一服务器进行后续请求。
- Date 字段
- 创建 HTTP 报文的日期和时间
- 请求首部字段
- Accept 字段
- 客户端接受的媒体类型和优先级(加权)
- text/html q=0.3,text/plain
- Accept-Encoding 字段
- 客户端接受的内容编码和优先级(加权)
- gzip
- Authorization 字段
- 客户端的认证信息
- Host 字段
- 请求的资源所处的互联网主机名和端口号
- HTTP1.1 规定的唯一一个必须包含的请求首部字段
- Range 字段
- 获取指定资源范围
- Referer 字段
- 可以用来防御 CSRF 攻击
- 告知服务器请求的原始资源 URI,可以知道 URI 是从哪个 Web 页面发起的
- User-Agent 字段
- 浏览器/用户代理等信息
- Cookie 字段
- 告知服务端,想获取 Cookie 支持
- Status=enable
- Accept 字段
- 响应首部字段
- Accept-Ranges 字段
- 能否处理范围请求
- bytes-- 能,none-- 不能
- Age 字段
- 源服务器多少秒前创建响应,单位为秒
- Location 字段
- 引导至指定 URL(重定向)
- Set-Cookie 字段
- 告知客户端信息
- NAME, expires, path, domain, HttpOnly
- HttpOnly 使得 JavaScript 脚本无法获得 Cookie,从而防止了跨站脚本攻击(XSS)
- Accept-Ranges 字段
- 实体首部字段
- 针对请求 / 响应报文的实体部分使用的首部
- Allow 字段
- 服务端允许的 HTTP 方法
- Content-Encoding 字段
- 服务端内容编码格式
- Content-Length 字段
- 实体主题部分大小,单位字节
- Content-Range 字段
- 针对范围请求,指定返回实体的部分
- Content-Type 字段
- 实体主题内对象的媒体类型
请求方式
GET 与 POST 的区别
GET 一般用来从服务器上获取资源,POST 一般用来创建资源;
- GET 是幂等的,即读取同一个资源,总是得到相同的数据,而 POST 不是幂等的。
- 从请求参数形式上看,GET 请求的数据会附在
URL 之后
;而 POST 请求会把提交的数据则放置在是 HTTP 请求报文的请求体
中。 - POST 的安全性要比 GET 的安全性高,因为 GET 请求提交的数据将明文出现在 URL 上,而 POST 请求参数则被包装到请求体中,相对更安全。
- GET 的历史参数会保留在浏览器历史中
- GET 请求的长度受限于浏览器或服务器对 URL 长度的限制,允许发送的数据量比较小,而 POST 请求则是没有大小限制的。
从攻击的角度,无论是 GET 还是 POST 都不够安全,因为 HTTP 本身是明文协议。每个 HTTP 请求和返回的每个 byte 都会在网络上明文传播,不管是 url,header 还是 body。
为了避免传输中数据被窃取,必须做从客户端到服务器的端端加密。业界的通行做法就是 HTTPS —— 即用 SSL 协议协商出的密钥加密明文的 http 数据。
HEAD
- HEAD 方法与 GET 方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求的实体数据,只会传回响应头,也就是资源的“元信息”。
- HEAD 方法可以看 做是 GET 方法的一个 “简化版” 或者 “轻量版”。因为它的响应头与 GET 完全相同,所以可以用在很多并不真正需要资源的场合,避免传输 body 数据的浪费。
- 比如,想要检查一个文件是否存在,只要发个 HEAD 请求就可以了,没有必要用 GET 把整个文件都取下来。再比如,要检查文件是否有最新版本,同样也应该用 HEAD,服务器会在响应头里把文件的修改时间传回来。
HTTP 0.9/1.0/1.1
HTTP 0.9
- 第一个版本,极简单
- 只允许 get
- 没有请求头,因此只支持纯文本;
- 具有典型的无状态性,每个事务独立处理,处理完后就释放
HTTP 1.0
- 支持请求头
- 响应对象不止文本
- 支持 GET/HEAD/POST 方法
- 支持长连接(但默认短)
- 支持缓存
- 身份认证
HTTP 1.1
- 持久连接 / 长连接
- 在一个 TCP 连接上可以传送多个 HTTP 请求和响应。使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
- 请求管道化
- 增加缓存处理(新的字段如 cache-control)
- 增加 Host 字段、支持断点传输等
- 提供范围请求功能,减少带宽浪费
HTTP 2.0/3.0
HTTP 2.0
-
二进制分帧
- 将响应报文划分为两个帧,首部 Header 和消息负载 Payload,并采用二进制编码
-
-
头部压缩
- HTTP1.1 中 Header 部分字段固定、字段值重复、字段是 ASCII 码,效率低
-
HTTP2.0 使用 HPACK 算法压缩头部
-
静态表/静态字典
- 为高频出现在头部的字符串建立了一张静态表,共 61 组
- 动态表/动态字典
- 不在静态表范围内的头部字符串自行构建动态表,Index 从 62 起
- 前提:必须是同一个连接上,重复传输完全相同的 HTTP 头部
- 动态表过大,占用内存过大,影响性能
- 通过 http2_max_requests 配置,限制一个连接上传输的请求数量。请求数达到上限,则关闭 HTTP2.0 连接来释放内存
-
Huffman 编码
-
-
- HTTP1.1 中 Header 部分字段固定、字段值重复、字段是 ASCII 码,效率低
-
并发传输(多路复用 / 连接共享)
- 多路复用前,文件是串行传输的,请求 a 文件,b 文件只能等待,并且连接数过多。引入多路复用,a 文件 b 文件可以同时传输。
- 队头阻塞问题
- HTTP1.1 的队头阻塞
- HTTP1.1 基于请求 - 响应模型,同一个连接中,HTTP 收到上一个请求的响应才能开始下一个请求
- HTTP2.0 在 HTTP 层面解决了 1.1 的队头阻塞问题,但 TCP 层仍然存在队头阻塞问题,彻底解决需要放弃使用 TCP => HTTP3.0
- HTTP1.1 的队头阻塞
- 三个概念
- Stream,Message,Frame
- 一个 TCP 连接中包含一个或多个 Stream
- Stream 里包含一个或多个 Message,Message 对应 HTTP1.1 中的请求或响应,由 HTTP 头部和体组成
- Message 里包含一个或多个 Frame,Frame 为 HTTP2.0 最小单位,以二进制格式存放 HTTP1.1 中的头部和体
- HTTP2.0 中,不同的 Stream 的帧可以乱序发送,因此可以并发不同的 Stream
- 原因在于每个帧的头部都会携带 Stream ID,而同一 Stream 内部的帧必须是严格有序的
- 可以类比 kafka 中的分区(内部的帧数据)有序而不保证主题(Stream)有序
- 原因在于每个帧的头部都会携带 Stream ID,而同一 Stream 内部的帧必须是严格有序的
- CS 双方都可以建立 Stream,客户端 StreamID 必须为奇数,服务器为偶
- 可以通过帧头的标志位对每个 Stream 设置优先级
- 例如先传 HTML/CSS,再传图片,提高用户体验
- HTTP2.0 实现 100 个并发 Stream,只需要建立一次 TCP 连接;而 1.1 需要建立 100 次,每个连接都要经过 TCP 握手、慢启动、SSL/TLS 握手
- Nginx 中可以通过 http2_max_concurrent_streams 配置 Stream 上限
-
服务器主动推送
- HTTP1.1 不支持服务器主动推送资源,只能客户端发起请求
- 服务器推送资源时,通过 PUSH_PROMISE 帧传输 HTTP 头部,并通过 Promised Stream ID 告知客户端,接下来会在哪个偶数 Stream ID 中发送消息体
HTTP 3.0
- HTTP2.0 的问题
- 队头阻塞
- 一个 TCP 连接中包含多个请求,当 TCP 丢包,需要等待重传,就会阻塞该 TCP 连接中的所有请求
- TCP 和 TLS 的握手延迟
- 发起 HTTP 请求需要经过 TCP 三次握手和 TLS(1.2)四次握手,总共需要 3 个 RTT 时延才能发出请求数据
- 拥塞控制的慢启动
- 网络迁移需要重新连接
- IP 地址或端口变动,需要重新握手
- 队头阻塞
- QUIC
- 0RTT
- 无队头阻塞
- QUIC 协议也是可以在同一连接上并发多个 Stream
- UDP 不关心数据包顺序和丢包问题
- QUIC 保证数据包可靠,每个数据包都有一个序号唯一标识。当某个数据包丢失,即使该 Stream 中的其他数据包到达,数据也无法被读取,直到重传丢失的包成功
- 相比于 HTTP2.0,多个 Stream 间没有依赖,丢包只会影响当前 Stream
- 更快的连接建立
- HTTP3.0 传输前需要 QUIC 握手, 需要 1RTT,目的是确认双方的连接 ID
- QUIC 内部包含了 TLS,并且使用的是 TLS1.3,因此只需一个 RTT 就可以同时完成建立连接与密钥协商。
- 在第二次连接时,数据包可以和握手信息一起发送,达到 0RTT
- 连接迁移
- QUIC 协议中通过连接 ID 标识双方,因此 IP/端口变更后,只要仍有上下文信息(连接 ID、TLS 密钥),就可以复用原连接
- HTTP3.0 的帧头只有类型和长度两个字段
- 头部压缩,升级为 QPACK 算法,和 HPACK 类似。
- 静态表由 61 项扩大到 91 项
- 由于动态表具有时序性,如果首次请求丢包,导致无法解码出 HPACK 头部,将阻塞请求,QPACK 解决了这一问题
- QUIC 有两个特殊的单向流,一个是 QPACK Encoder Stream,用来将一个 KV 传递给对方;另一个 QPACK Decoder Stream,用来响应,告诉对方刚才的 KV 已经更新到本地动态表中,从而确认加入动态表成功
- 我的理解:因为动态表的确能节约很大一部分编码、传输上的开销,所以即使额外维护两个单向流,也是值得的。本质上是一种 trade-off
HTTP 缓存
- 客户端将第一次请求的 URL 和响应数据分别作为 KV,保存在本地磁盘
- 服务端在回应时会通过头部字段返回估算的数据过期时间
- 若过期,则请求头带 Etag,附上第一次响应头的摘要
- 服务端进行判断,若仍有效,则返回不含有消息体 body 的 304 Not Modified,这样一来就减少了响应资源的传输带宽
HTTPS
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):可以理解为 HTTP + SSL/TLS
HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。
SSL/TLS 握手
- SSL(Secure Socket Layer,安全套接字层):SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。
- TLS(Transport Layer Security,传输层安全):其前身是 SSL。
HTTPS 的连接过程 RSA (非对称加密算法)“四次握手”
- ClientHello
- 发送:客户端使用的 TLS 版本号、支持的密码套件、随机数 Client Random
- ServerHello
- 发送:确认 TLS 版本号是否支持、从密码套件中选择一个(密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法)、随机数 Server Random
- 发送:服务端的证书(包括公钥)
- Server Hello Done
- 客户端回应
- 通过证书链验证服务端身份、随机数 pre-master,并用服务器公钥加密,发送给服务端,双方根据三个随机数生成会话密钥 Master Secret,之后开始使用密钥进行通话
- 再把之前发送过的数据的摘要,用会话密钥加密发送给服务端,确认之前的信息没有被篡改
- 服务器最后回应
- 同客户端回应
- 缺陷
- 不支持前向保密
- 长期使用的主密钥泄漏会导致过去的会话密钥泄漏
- 客户端传随机数给服务端时用的是公钥加密,一旦服务器私钥泄漏,所有通讯密文都会被破解
- 不支持前向保密
HTTP 和 HTTPS 的区别
- HTTPS 协议需要到 CA 申请证书,一般免费证书较少, 因而需要一定费用。
- HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443
- HTTP 的连接很简单,是无状态的;HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。
说说 OSI 七层、TCP/IP 四层的关系和区别?
OSI 七层从下往上依次是:物理层
、数据链路层
、网络层
、传输层
、会话层
、表示层
、应用层
。
TCP/IP 四层从下往上依次是:网络接口层
、网络层
、传输层
、应用层
特点:
- 层与层之间相互独立又相互依靠
- 上层依赖于下层,下层为上层提供服务
TCP/IP 四层是 OSI 七层的简化版,已经成为事实国际标准
OSI 七层举例
- 应用层
- HTTP、FTP、SMTP、TELNET、DNS、SOCKS、DHCP
- 表示层
- 会话层
- SSL/TLS、RPC
- 传输层
- TCP/UDP
- 网络层(IP 地址)
- IPv4/v6、ICMP、IGMP、RIP、OSPF、BGP、MPLS
- 数据链路层(MAC 物理地址)
- ARP、RARP、PPP
- 物理层(比特流)
TCP / UDP
说说 TCP 与 UDP 的区别?
TCP 如何实现数据的可靠性
一句话:通过 校验和
、序列号
、确认应答
、超时重传
、连接管理
、流量控制
、拥塞控制
等机制来保证可靠性。
校验和
在数据传输过程中,将发送的数据段都当做一个 16 位的整数,将这些整数加起来,并且前面的进位不能丢弃,补在最后,然后取反,得到校验和。
发送方:在发送数据之前计算校验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方进行比较。
序列号 seq
TCP 传输时将每个数据包都进行了编号,这就是序列号。序列号的作用不仅仅是应答作用,有了序列号能够将接收到的数据根据序列号进行排序,并且去掉重复的数据。
确认应答 ack
TCP 传输过程中,每次接收方接收到数据后,都会对传输方进行确认应答,也就是发送 ACK 报文,这个 ACK 报文中带有对应的确认序列号,告诉发送方,接收了哪些数据,下一次数据从哪里传。
超时重传
TCP 在发送数据包后会启动一个定时器,如果在规定时间内没有收到接收方的确认应答 ACK,就会重新发送数据包,直到收到确认应答为止。
连接管理
就是指三次握手、四次挥手的过程。
流量控制
以接收方为主体,基于滑动窗口
如果发送方的发送速度太快,会导致接收方的接收缓冲区填充满了,这时候继续传输数据,就会造成大量丢包,进而引起丢包重传等等一系列问题。TCP 支持根据接收端的处理能力来决定发送端的发送速度,这就是流量控制机制。
具体实现方式:接收端将自己的接收缓冲区大小(还能接受多少字节数据)放入 TCP 首部的『窗口大小 rwnd』(Receiver Window)字段中,通过 ACK 通知发送端。
拥塞控制
以发送方为主体,基于拥塞窗口
TCP 传输过程中一开始就发送大量数据,如果当时网络非常拥堵,可能会造成拥堵加剧。
TCP 发送方根据网络质量来动态控制拥塞窗口的大小
TCP 如何提高传输效率
也就是问 TCP 的流量控制
一句话:TCP 协议提高效率的方式有 滑动窗口
、快重传
、延迟应答
、捎带应答
等。
滑动窗口
如果每一个发送的数据段,都要收到 ACK 应答之后再发送下一个数据段,这样的话我们效率很低,大部分时间都用在了等待 ACK 应答上了。
为了提高效率我们可以一次发送多条数据,这样就能使等待时间大大减少,从而提高性能。 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。
快重传
基于滑动窗口,应该是对超时重传的一种优化
快重传
也叫 高速重发控制
。
那么如果出现了丢包,需要进行重传。一般分为两种情况:
情况一:数据包已经抵达,ACK 丢了。这种情况下,发送方不会进行重传,部分 ACK 丢了并不影响,因为可以通过后续的 ACK 进行确认;
情况二:数据包直接丢了。发送端会连续收到多个相同的 ACK 确认,发送端立即将对应丢失的数据重传。
延迟应答
如果接收数据的主机立刻返回 ACK 应答,这时候返回的窗口大小可能比较小。
- 假设接收端缓冲区为 1M,一次收到了 512K 的数据;如果立刻应答,返回的窗口就是 512K;
- 但实际上可能处理端处理速度很快,10ms 之内就把 512K 的数据从缓存区消费掉了;
- 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
- 如果接收端稍微等一会在应答,比如等待 200ms 再应答,那么这个时候返回的窗口大小就是 1M;
窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。
捎带应答
在延迟应答的基础上,很多情况下,客户端服务器在应用层也是一发一收的。这时候常常采用捎带应答的方式来提高效率,而 ACK 响应常常伴随着数据报文共同传输。如:三次握手。
TCP 拥塞控制
拥塞避免机制、慢开始、设置门限变量、区分不同的丢包事件
拥塞造成丢包和分组延迟增大,发送方丢过丢包时间来判断拥塞
- 重传定时器超时
- 收到三个相同(重复)的 ACK
发送方维持一个拥塞窗口 cwnd(Congestion Window)来限制发送窗口,从而间接控制发送速度;拥塞窗口动态变化,取决于网络的拥塞程度
调节策略:AIMD 拥塞避免
- 乘法减小
- 发送包检测到丢包后,cwnd 大小减半
- 加法增大
- 若无丢包,每经过一个 RTT(Round-Trip Time,往返时延),将 cwnd 增大一个 MSS,直到检测到丢包
慢开始/慢启动
在新建连接上指数增大 cwnd,直到检测到丢包
设置慢开始门限状态变量 ssthresh
当 cwnd < ssthresh 时,使用慢开始算法。
当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法
当遇到丢包时,ssthresh 变为当前 cwnd 的一半
区分不同的丢包事件
- 超时:网络交付能力差
- cwnd 直接降到初始值,然后指数增大
- 收到重复三个 ACK:网络仍有一定交付能力
- cwnd 减半,然后加法增大,是为快重传、快恢复
三次握手和四次挥手全过程
三次握手

四次挥手

为什么 TCP 连接需要三次握手,两次不可以嘛?
两次握手只能保证单向连接是畅通的
第一步,客户端给服务端发送一条消息:你好,服务端。 第二步,服务端收到消息,同时给客户端回复一条消息:收到!你好客户端。
这样的两次握手过程, 客户端给服务端打招呼,服务端收到了,说明客户端可以正常给服务端发送数据。但是服务端给客户端打招呼,服务端没有收到反馈,也就不能确保服务端是否能正常给客户端发送消息。
只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据
第三步,客户端收到服务端发送的消息,回复:收到!这样就证明了客户端能正常收到服务端的消息。