- 1. 计算机网络模型
- 2. 应用层
- 2.1 HTTP 报文有哪些部分?
- 2.2 HTTP 常见的状态码有哪些?
- 2.3 状态码 301 和 302 的区别是什么?
- 2.4 状态码 502 和 504 的区别是什么?
- 2.5 HTTP 常用的请求方式有哪些?
- 2.6 GET 请求和 POST 请求的区别?
- 2.7 解释一下 HTTP 长连接和短连接?
- 2.8 HTTP1.0 和 HTTP1.1 的区别是什么?
- 2.9 HTTP1.1 和 HTTP2.0 的区别是什么?
- 2.10 HTTP 和 HTTPS 的区别?
- 2.11 HTTPS 的优缺点?
- 2.12 HTTPS 的原理/握手过程是什么?
- 2.13 介绍一下 DNS 及其工作流程是什么?
- 2.14 HTTP 是无状态的吗?
- 2.15 什么是 Cookie 和 Session?
- 2.16 Cookie 和 Session 是如何配合的呢?
- 2.17 Cookie 和 Session 有什么区别?
- 2.18 Local Storage 和 Cookie 有什么区别?
- 2.19 前端是如何存储 JWT 的?
- 2.20 JWT 令牌和传统方式有什么区别?
- 2.21 JWT 令牌如果泄露了怎么解决?
- 2.22 Nginx 有哪些负载均衡算法?
- 3. 传输层
- 3.1 TCP 和 UDP 的区别?
- 3.2 TCP 和 UDP 对应的应用场景是什么?
- 3.3 TCP 报文段的头部是什么?
- 3.4 TCP 的三次握手过程是什么?
- 3.5 为什么需要三次握手,而不是两次?
- 3.6 为什么需要三次握手,而不是四次?
- 3.7 三次握手时如果客户端第三次发送的确认包丢失了会怎样?
- 3.8 客户端发送的第一个 SYN 报文,服务器没有收到怎么办?
- 3.9 第一次握手过程中服务器内部做了哪些工作?
- 3.10 什么是 SYN 洪泛攻击?如何防范?
- 3.11 TCP 的四次挥手过程是什么?
- 3.12 为什么连接的时候是三次握手,关闭的时候却是四次挥手?
- 3.13 如果第三次挥手一直没发,会发生什么?
- 3.14 主动断开连接时若客户端 FIN 包丢失,服务器的状态是什么?
- 3.15 为什么四次挥手后客户端的 TIME_WAIT 状态必须等待 2MSL?
- 3.16 如果已经建立了连接,但是客户端出现故障了怎么办?
- 3.17 TIME_WAIT 是服务器还是客户端的状态?
- 3.18 TCP 和 UDP 的区别是什么?
- 3.19 TCP 协议如何保证可靠性,即如何实现可靠传输?
- 3.20 详细讲一下 TCP 的滑动窗口?
- 3.21 详细讲一下拥塞控制?
- 4. 网络场景
- 5. 网络攻击
计算机网络常见面试题总结,涉及网络模型、应用层、传输层等内容,文章将不断更新。
1. 计算机网络模型
计算机网络体系可以大致分为三种:OSI 七层模型、TCP/IP 四层模型和五层模型。
- OSI 七层模型:大而全,但是比较复杂,提出的也只是概念理论上的分层,并没有提供具体的实现方案。
- TCP/IP 四层模型:是由实际应用发展总结出来的,从实质上讲,TCP/IP 只有最上面两层,最下面一层没有什么具体内容,TCP/IP 参考模型没有真正描述这一层的实现。
- TCP/IP 五层模型:五层模型只出现在计算机网络教学过程中,这是对七层模型和四层模型的一个折中,既简洁又能将概念阐述清楚。
七层网络体系结构各层的主要功能:
- 应用层:为应用程序提供交互服务,负责给应用程序提供统一的接口。在互联网中的应用层协议有很多,如域名系统 DNS,支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议等。
- 表示层:主要负责数据格式的转换,如加密解密、转换翻译、压缩解压缩等。
- 会话层:负责在网络中的两节点之间建立、维持和终止通信,如服务器验证用户登录便是由会话层完成的。
- 运输层:有时也译为传输层,向主机进程提供通用的数据传输服务,负责端到端的数据传输。该层主要有以下两种协议:
- TCP:提供面向连接的、可靠的数据传输服务。
- UDP:提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性。
- 网络层:负责数据的路由、转发、分片,选择合适的路由和交换结点,确保数据及时传送。主要包括 IP 协议。
- 数据链路层:数据链路层通常简称为链路层。负责数据的封帧和差错检测以及 MAC 寻址,将网络层传下来的 IP 数据包组装成帧,并在相邻节点的链路上传送帧。
- 物理层:负责在物理线路中传输数据帧,实现相邻节点间比特流的透明传输,尽可能屏蔽传输介质和通信手段的差异。
TCP/IP 网络通常是由上到下分成 4 层,分别是应用层(对应 OSI 的应用层、表示层、会话层),传输层(对应 OSI 的传输层),网络层(对应 OSI 的网络层)和网络接口层(对应 OSI 的数据链路层、物理层)。
2. 应用层
2.1 HTTP 报文有哪些部分?
HTTP 报文分为请求报文和响应报文:
- 请求报文
- 请求行:包含请求方法、请求目标(URL 或 URI)和 HTTP 协议版本。
- 请求头部:包含关于请求的附加信息,如 Host、User-Agent、Content-Type 等。
- 空行:请求头部和请求体之间用空行分隔。
- 请求体:可选,包含请求的数据,通常用于 POST 请求等需要传输数据的情况。
- 响应报文
- 状态行:包含 HTTP 协议版本、状态码和状态信息。
- 响应头部:包含关于响应的附加信息,如 Content-Type、Content-Length 等。
- 空行:响应头部和响应体之间用空行分隔。
- 响应体:包含响应的数据,通常是服务器返回的 HTML、JSON 等内容。
2.2 HTTP 常见的状态码有哪些?
常见状态码:
- 200:服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
- 301:请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置(永久移动)。
- 302:服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求(临时移动)。
- 400:客户端请求有语法错误,不能被服务器所理解。
- 403:服务器收到请求,但是拒绝提供服务。
- 404:服务器找不到请求的网页。
- 405:请求的方法类型不支持。
- 500:服务器内部遇到错误,无法完成请求。
状态码开头代表类型:
类别 | 原因短语 | |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
2.3 状态码 301 和 302 的区别是什么?
- 共同点:301 和 302 状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的 URL 地址,这个地址可以从响应的 Location 首部中获取(用户看到的效果就是他输入的地址 A 瞬间变成了另一个地址 B)。
- 不同点:301 表示永久重定向,即旧地址 A 的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址。302 表示旧地址 A 的资源还在(仍然可以访问),这个重定向只是临时地从旧地址 A 跳转到地址 B,搜索引擎会抓取新的内容而保存旧的网址。SEO 中 302 好于 301。
重定向原因:
- 网站调整(如改变网页目录结构)。
- 网页被移到一个新地址。
- 网页扩展名改变(如应用需要把
.php
改成.html
或.shtml
)。
2.4 状态码 502 和 504 的区别是什么?
- 502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。例如假设 Nginx 是代理服务器,收到客户端的请求后,将请求转发到后端服务器(Tomcat 等),当 Nginx 收到了无效的响应时,就返回 502。
- 504 Gateway Time-out:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器收到响应。例如当 Nginx 超过自己配置的超时时间,还没有收到请求时,就返回 504。
2.5 HTTP 常用的请求方式有哪些?
方法 | 作用 |
---|---|
GET | 请求获取指定资源,通常用于获取数据 |
POST | 向服务器提交数据,通常用于提交表单数据或进行资源的创建 |
PUT | 向服务器更新指定资源,通常用于更新已存在的资源 |
DELETE | 请求服务器删除指定资源 |
HEAD | 类似于 GET 请求,但只返回资源的头部信息,用于获取资源的元数据而不获取实际内容 |
PATCH | 对资源进行部分修改 |
OPTIONS | 查询指定的 URL 支持的方法 |
CONNECT | 要求用隧道协议连接代理 |
TRACE | 服务器会将通信路径返回给客户端 |
为了方便记忆,可以将 PUT、DELETE、POST、GET 理解为客户端对服务端的增删改查:
- PUT:上传文件,向服务器添加数据。
- DELETE:删除文件。
- POST:传输数据,向服务器提交数据,对服务器数据进行更新。
- GET:获取资源,查询服务器资源。
2.6 GET 请求和 POST 请求的区别?
使用上的区别:
- 作用:GET 用于获取资源,而 POST 用于传输实体。
- 参数:GET 使用 URL 或 Cookie 传参,而 POST 将数据放在 Request Body 中,这个是因为 HTTP 协议用法的约定。
- 缓存:GET 请求会被浏览器主动缓存,而 POST 一般不会,除非手动设置。
- 请求长度:GET 方式提交的数据有长度限制,基本为 2kb,而 POST 的数据则可以非常大,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。
- 安全性:POST 比 GET 安全,因为数据在地址栏上不可见,而 GET 的参数直接暴露在 URL 上。这个说法没毛病,但依然不是 GET 和 POST 本身的区别。
本质区别:GET 和 POST 最大的区别主要是 GET 请求是幂等性的,POST 请求不是。幂等性是指一次和多次请求某一个资源应该具有同样的副作用,简单来说意味着对同一 URL 的多个请求应该返回同样的结果。GET 方法是只读操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的,所以可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如 Nginx),而且在浏览器中 GET 请求可以保存为书签。
2.7 解释一下 HTTP 长连接和短连接?
在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源,如 JavaScript 文件、图像文件、CSS 文件等,当浏览器每遇到这样一个 Web 资源,就会建立一个 HTTP 会话。
但从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。具体地说,Keep-Alive 实现了长连接功能,可以使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,避免了连接建立和释放的开销,使用长连接的 HTTP 协议,会在响应头加入这行代码:Connection: Keep-Alive
。
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(例如 Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。
HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。
2.8 HTTP1.0 和 HTTP1.1 的区别是什么?
- 长连接:HTTP1.1 支持长连接(Persistent Connection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启
Connection: Keep-Alive
,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。 - 缓存处理:在 HTTP1.0 中主要使用
header
里的If-Modified-Since, Expires
来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略,可供选择的缓存头来控制缓存策略。 - 带宽优化及网络连接的使用:HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了
range
头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 - 错误通知的管理:在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突,410(Gone)表示服务器上的某个资源被永久性的删除。
- Host 头处理:在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(Hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。
2.9 HTTP1.1 和 HTTP2.0 的区别是什么?
HTTP2.0 相比 HTTP1.1 支持的特性:
- 新的二进制格式:HTTP1.1 的解析是基于文本的。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用:即连接共享,每一个 Request 都是用作连接共享机制的。一个 Request 对应一个 ID,这样一个连接上可以有多个 Request,每个连接的 Request 可以随机的混杂在一起,接收方可以根据 Request 的 ID 将 Request 再归属到各自不同的服务端请求里面。
- 头部压缩:HTTP1.1 的头部(Header)带有大量信息,而且每次都要重复发送。HTTP2.0 使用 Encoder 来减少需要传输的 Header 大小,通讯双方各自缓存一份 Header Fields 表,既避免了重复 Header 的传输,又减小了需要传输的大小。
- 服务端推送:服务器除了对最初请求的响应外,服务器还可以额外地向客户端推送资源,而无需客户端明确的请求。
2.10 HTTP 和 HTTPS 的区别?
HTTP | HTTPS | |
---|---|---|
端口 | 80 | 443 |
安全性 | 明文传输无加密,安全性较差 | 在 TCP 和 HTTP 之间加入了 SSL/TLS 安全协议,有加密机制,安全性较高 |
资源消耗 | 较少 | 由于加密处理,资源消耗更多 |
是否需要证书 | 不需要 | 需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的 |
协议 | 运行在 TCP 协议之上 | 运行在 SSL 协议之上,SSL 运行在 TCP 协议之上 |
2.11 HTTPS 的优缺点?
优点:
- 安全性:
- 使用 HTTPS 协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
- HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 HTTP 协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
- HTTPS 是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
- SEO 方面:谷歌曾在 2014 年调整搜索引擎算法,并称比起同等 HTTP 网站,采用 HTTPS 加密的网站在搜索结果中的排名将会更高。
缺点:
- 在相同网络环境中,HTTPS 相比 HTTP 无论是响应时间还是耗电量都有大幅度上升。
- HTTPS 的安全是有范围的,在黑客攻击、服务器劫持等情况下几乎起不到作用。
- 在现有的证书机制下,中间人攻击依然有可能发生。
- HTTPS 需要更多的服务器资源,也会导致成本的升高。
2.12 HTTPS 的原理/握手过程是什么?
- 第一次握手:客户端首先生成第一个随机数
Client Random
,然后请求 HTTPS 网址,例如:https://www.baidu.com
,然后连接到服务器的 443 端口(HTTPS 默认端口,类似于 HTTP 的 80 端口)。 - 采用 HTTPS 协议的服务器必须要有一套数字 CA(Certification Authority)证书。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开的。证书本身也附带一个证书电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被篡改。
- 第二次握手:服务器响应客户端请求,将证书传递给客户端,证书包含公钥和大量其他信息,比如证书颁发机构信息,公司信息和证书有效期等,此外还向客户端发送第二个随机数
Server Random
。 - 客户端解析证书并对其进行验证。如果证书不是可信机构颁布,或者证书中的域名与实际域名不一致,或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥 A。然后客户端还会继续生成第三个随机码
Pre-master Key
,并使用公钥 A 将其加密。 - 第三次握手:客户端把加密后的随机码
Pre-master Key
发送给服务器,作为后面对称加密的密钥。 - 服务器在收到随机码
Pre-master Key
之后会使用私钥 B 将其解密,现在客户端和服务器都拥有三个随机数,接着就用双方协商的加密算法,各自生成本次通信的会话秘钥。 - 第四次握手:服务器向客户端发送最后的信息,表示随后的信息都将用会话秘钥加密通信,且服务器的握手阶段已经结束,同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。
- 经过以上这些步骤,客户端和服务器终于建立了安全连接,完美解决了对称加密的密钥泄露问题,接下来就可以用对称加密愉快地进行通信了。
2.13 介绍一下 DNS 及其工作流程是什么?
DNS 的全称是 Domain Name System(域名系统),它是互联网中用于将域名转换为对应 IP 地址的分布式数据库系统,默认端口号为 53。DNS 扮演着重要的角色,使得人们可以通过易记的域名访问互联网资源,而无需记住复杂的 IP 地址。
DNS 基于 UDP 协议实现,因为基于 UDP 实现 DNS 能够提供低延迟、简单快速、轻量级的特性,更适合 DNS 这种需要快速响应的域名解析服务。尽管 UDP 存在丢包和数据包损坏的风险,但在 DNS 的设计中,这些风险是可以被容忍的,DNS 使用了一些机制来提高可靠性,例如查询超时重传、请求重试、缓存等,以确保数据传输的可靠性和正确性。
域名解析的工作流程如下:
- 客户端首先会发出一个 DNS 请求,问
www.example.com
的 IP 是啥,并发给本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。 - 本地 DNS 服务器收到客户端的请求后,如果缓存里的表格能找到
www.example.com
,则它直接返回该域名对应的 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器,根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。 - 根 DNS 收到来自本地 DNS 的请求后,发现后缀是
.com
,于是将.com
顶级域 DNS 服务器地址返回给本地 DNS。 - 本地 DNS 收到顶级域 DNS 服务器的地址后,继续向顶级域 DNS 服务器发起请求询问
www.example.com
的地址。 - 顶级域 DNS 服务器发现后缀是
.example.com
,于是将负责.example.com
区域的权威 DNS 服务器地址返回给本地 DNS。 - 本地 DNS 继续转向权威 DNS 服务器进行询问,权威 DNS 服务器查询后将对应的 IP 地址
X.X.X.X
告诉给本地 DNS。 - 本地 DNS 最后再将 IP 地址返回客户端,客户端和目标 IP 建立连接。
2.14 HTTP 是无状态的吗?
HTTP 是无状态的,这意味着每个请求都是独立的,服务器不会在多个请求之间保留关于客户端状态的信息,即在每个 HTTP 请求中,服务器不会记住之前的请求或会话状态,因此每个请求都是相互独立的。
虽然 HTTP 本身是无状态的,但可以通过一些机制来实现状态保持,其中最常见的方式是使用 Cookie 和 Session 来跟踪用户状态。通过在客户端存储会话信息或状态信息,服务器可以识别和跟踪特定用户的状态,以提供一定程度的状态保持功能。
2.15 什么是 Cookie 和 Session?
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)。
- 个性化设置(如用户自定义设置、主题等)。
- 浏览器行为跟踪(如跟踪分析用户行为等)。
Session 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束。
2.16 Cookie 和 Session 是如何配合的呢?
用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在则自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
2.17 Cookie 和 Session 有什么区别?
- 存储位置:Cookie 保存在客户端(浏览器),当浏览器向服务器发送请求时,会自动附带Cookie中的数据。Session 保存在服务器端,服务器为每个用户分配一个唯一的 Session ID,这个 ID 通常通过 Cookie 或 URL 重写的方式发送给客户端,客户端后续的请求会带上这个 Session ID,服务器根据 ID 查找对应的 Session 数据。
- 存取方式:Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserID 等。
- 生命周期:Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效。
- 安全性:Cookie 存储在客户端,容易受到 XSS(跨站脚本攻击)的威胁,可以通过设置 HttpOnly 属性来防止 JavaScript 访问,减少 XSS 攻击的风险,但仍然可能受到 CSRF(跨站请求伪造)的攻击;Session 存储在服务端,安全性相对 Cookie 要好一些,但仍然需要防范 Session 劫持(获取他人的 Session ID)和会话固定攻击。
- 存储大小:单个 Cookie 保存的数据不能超过 4KB,且大多数浏览器对每个域名的总 Cookie 数量也有限制。Session 可存储数据远高于 Cookie,理论上不受数据大小的限制,主要受限于服务器的内存大小。
- 性能:使用 Cookie 时,因为数据随每个请求发送到服务器,可能会影响网络传输效率,尤其是在 Cookie 数据较大时。使用 Session 时,因为数据存储在服务器端,每次请求都需要查询服务器上的 Session 数据,这可能会增加服务器的负载,特别是在高并发场景下。
2.18 Local Storage 和 Cookie 有什么区别?
- 存储容量:Cookie 的存储容量通常较小,每个 Cookie 的大小一般不能超过 4KB。LocalStorage 的存储容量通常较大,一般限制在几 MB 左右。
- 数据发送:Cookie 在每次 HTTP 请求中都会自动发送到服务器,这使得 Cookie 适合用于在客户端和服务器之间传递数据。LocalStorage 的数据不会自动发送到服务器,它仅在浏览器端存储数据,因此 Local Storage 适合用于在同一域名下的不同页面之间共享数据。
- 生命周期:Cookie 可以设置一个过期时间,使得数据在指定时间后自动过期。LocalStorage 的数据将永久存储在浏览器中,除非通过 JavaScript 代码手动删除。
- 安全性:Cookie 的安全性较低,因为 Cookie 在每次 HTTP 请求中都会自动发送到服务器,存在被窃取或篡改的风险。LocalStorage 的数据仅在浏览器端存储,不会自动发送到服务器,相对而言更安全一些。
2.19 前端是如何存储 JWT 的?
互联网服务离不开用户认证,JWT(JSON Web Token)是目前最流行的跨域认证解决方案,JWT 令牌由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。其中,头部和载荷均为 JSON 格式,使用 Base64 编码进行序列化,而签名部分是对头部、载荷和密钥进行签名后的结果。
传统的 Session 认证流程如下:
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前会话(Session)里面保存相关数据,比如用户角色、登录时间等等。
- 服务器向用户返回一个
session_id
,写入用户的 Cookie。 - 用户随后的每一次请求,都会通过 Cookie,将
session_id
传回服务器。 - 服务器收到
session_id
,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求 Session 数据共享,每台服务器都能够读取 Session。
举例来说,A 网站和 B 网站是同一家公司的关联服务,现在要求用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 Session 数据持久化,写入数据库或别的持久层,各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大,另外如果持久层挂了,就会导致单点失败。
另一种方案是服务器索性不保存 Session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
客户端收到服务器返回的 JWT,可以储存在 Local Storage 里面,也可以储存在 Cookie 里面,还可以存储在 Session Storage 里面:
- Local Storage
- 优点:Local Storage 提供了较大的存储空间(一般为 5MB),且不会随着 HTTP 请求一起发送到服务器,因此不会出现在 HTTP 缓存或日志中。
- 缺点:存在 XSS(跨站脚本攻击)的风险,恶意脚本可以通过 JavaScript 访问到存储在 Local Storage 中的 JWT,从而盗取用户凭证。
- Session Storage
- 优点:与 Local Storage 类似,但仅限于当前浏览器窗口或标签页,当窗口关闭后数据会被清除,这在一定程度上减少了数据泄露的风险。
- 缺点:用户体验可能受影响,因为刷新页面或在新标签页打开相同应用时需要重新认证。
- Cookie
- 优点:可以设置 HttpOnly 标志来防止通过 JavaScript 访问,减少 XSS 攻击的风险;可以利用 Secure 标志确保仅通过 HTTPS 发送,增加安全性。
- 缺点:大小限制较小(通常 4KB),并且每次 HTTP 请求都会携带 Cookie,可能影响性能;设置不当可能会受到 CSRF(跨站请求伪造)攻击。
2.20 JWT 令牌和传统方式有什么区别?
- 无状态性:JWT 是无状态的令牌,不需要在服务器端存储会话信息。相反,JWT 令牌中包含了所有必要的信息,如用户身份、权限等。这使得 JWT 在分布式系统中更加适用,可以方便地进行扩展和跨域访问。
- 安全性:JWT 使用密钥对令牌进行签名,确保令牌的完整性和真实性,只有持有正确密钥的服务器才能对令牌进行验证和解析。这种方式比传统的基于会话和 Cookie 的验证更加安全,有效防止了 CSRF(跨站请求伪造)等攻击。
- 跨域支持:JWT 令牌可以在不同域之间传递,适用于跨域访问的场景。通过在请求的头部或参数中携带 JWT 令牌,可以实现无需 Cookie 的跨域身份验证。
2.21 JWT 令牌如果泄露了怎么解决?
- 及时失效令牌:当检测到 JWT 令牌泄露或存在风险时,可以立即将令牌标记为失效状态,服务器在接收到带有失效标记的令牌时,会拒绝对其进行任何操作,从而保护用户的身份和数据安全。
- 刷新令牌:JWT 令牌通常具有一定的有效期,过期后需要重新获取新的令牌。当检测到令牌泄露时,可以主动刷新令牌,即重新生成一个新的令牌,并将旧令牌标记为失效状态。
- 使用黑名单:服务器可以维护一个令牌的黑名单,将泄露的令牌添加到黑名单中。在接收到令牌时,先检查令牌是否在黑名单中,如果在则拒绝操作。这种方法需要服务器维护黑名单的状态,对性能有一定的影响,但可以有效地保护泄露的令牌不被滥用。
2.22 Nginx 有哪些负载均衡算法?
- 轮询:按照顺序依次将请求分配给后端服务器。这种算法最简单,但是也无法处理某个节点变慢或者客户端操作有连续性的情况。
- IP 哈希:根据客户端 IP 地址的哈希值来确定分配请求的后端服务器。适用于需要保持同一客户端的请求始终发送到同一台后端服务器的场景,如会话保持。
- URL 哈希:按访问的 URL 的哈希结果来分配请求,使每个 URL 定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。
- 最短响应时间:按照后端服务器的响应时间来分配请求,响应时间短的优先分配。适用于后端服务器性能不同的场景,能够将请求发送到响应时间快的服务器,实现负载均衡。
- 加权轮询:按照后端服务器的权重来分配请求,权重越高的服务器获得更多的请求。适用于后端服务器性能不同的场景,可以根据服务器权重分配请求,提高高性能服务器的利用率。
3. 传输层
3.1 TCP 和 UDP 的区别?
TCP | UDP | |
---|---|---|
是否连接 | 面向连接 | 无连接 |
是否可靠 | 可靠传输,使用流量控制和拥塞控制 | 不可靠传输,不使用流量控制和拥塞控制 |
是否有序 | 有序,消息在传输过程中可能会乱序,TCP 会重新排序 | 无序 |
传输速度 | 慢 | 快 |
连接对象个数 | 只能一对一通信 | 支持一对一、一对多、多对一和多对多交互通信 |
传输方式 | 面向字节流 | 面向报文 |
首部开销 | 首部开销大,最小 20 字节,最大 60 字节 | 首部开销小,仅 8 字节 |
适用场景 | 适用于要求可靠传输的应用,例如文件传输 | 适用于实时应用例如 IP 电话、视频会议、直播等 |
总结:TCP 用于在传输层有必要实现可靠传输的情况,UDP 用于对高速传输和实时性有较高要求的通信。TCP 和 UDP 应该根据应用目的按需使用。
3.2 TCP 和 UDP 对应的应用场景是什么?
- TCP 是面向连接的,能保证数据的可靠性交付,因此经常用于:
- FTP 文件传输。
- HTTP/HTTPS。
- SMTP 简单邮件传输。
- UDP 是无连接的,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:
- 包总量较少的通信,如 DNS、SNMP 等。
- 视频、音频等多媒体通信。
- 广播通信。
3.3 TCP 报文段的头部是什么?
TCP 报文段的头部至少 20 字节,常见字段包括:
- 源端口号和目的端口号:各占 16 位,用于标识通信双方的应用程序端口。
- 序列号(Sequence Number):占 32 位,用于标记发送的数据字节(不是报文段)在整个数据流中的位置(偏移量),用来解决网络包乱序问题。连接建立时选取一个随机初始序号(ISN),通过 SYN 包传给接收端主机,后续每发送一个字节序号递增。
- 确认应答号(Acknowledgment Number):占 32 位,表示期望接收的下一个字节的序列号,确保数据的有序确认,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被接收端正常接收,用来解决丢包的问题。例如接收方发送 ACK = 555 时说明已经收到了 554 及之前的字节。
- 数据偏移(Data Offset):占 4 位,指明 TCP 首部的长度,以 4 字节为单位,使得选项字段长度可变。
- 控制标志位:共有 6 个,包括 URG(紧急指针有效)、ACK(确认号有效,该位为 1 时,确认应答号的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1)、PSH(推送数据)、RST(复位连接,该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接)、SYN(同步序列号,该位为 1 时,表示希望建立连接,并在其序列号的字段进行序列号初始值的设定)和 FIN(终止连接,该位为 1 时,表示今后不会再有数据发送,希望断开连接,当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段)。
- 窗口大小(Window Size):占 16 位,表示接收方当前能接收的数据量,用于流量控制。
- 校验和(Checksum):占 16 位,对 TCP 首部和数据进行错误检测,确保传输过程中数据未被损坏。
- 紧急指针(Urgent Pointer):占 16 位,仅当 URG 标志为 1 时有效,指示紧急数据的末尾。
- 选项(Options):如最大报文段长度(MSS)、窗口缩放、时间戳、SACK 等,用于扩展 TCP 功能和优化传输性能。
3.4 TCP 的三次握手过程是什么?
- 第一次握手:首先服务器主动监听某个端口,处于
LISTEN
状态,客户端请求建立连接,向服务器发送一个同步报文(SYN = 1
),同时选择一个随机数seq = x
作为初始序列号,并进入SYN_SENT
(同步已发送)状态,等待服务器确认。 - 第二次握手:服务器收到连接请求报文后,如果同意建立连接,则向客户端发送同步确认报文(
SYN = 1, ACK = 1
),确认应答号为ack = x + 1
,同时选择一个随机数seq = y
作为初始序列号,此时服务器进入SYN_RCVD
(同步收到)状态。 - 第三次握手:客户端收到服务器的确认报文后,向服务器发送一个应答报文(
ACK = 1
),确认应答号为ack = y + 1
,序列号为seq = x + 1
,这段报文可以携带客户端到服务器的数据,服务器收到客户端的应答报文后,客户端和服务器进入ESTABLISHED
(已建立连接)状态,完成三次握手。
从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。
理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
3.5 为什么需要三次握手,而不是两次?
主要有三个原因:
- 避免历史连接(主要原因):防止已过期的连接请求报文突然又传送到服务器,因而产生错误和资源浪费。在双方两次握手即可建立连接的情况下,假设客户端发送报文段 A 请求建立连接,由于网络原因造成 A 暂时无法到达服务器,服务器接收不到请求报文段就不会返回确认报文段。客户端在长时间得不到应答的情况下重新发送请求报文段 B,这次 B 顺利到达服务器,服务器随即返回确认报文并进入
ESTABLISHED
状态,客户端在收到确认报文后也进入ESTABLISHED
状态,双方建立连接并传输数据,之后正常断开连接。此时姗姗来迟的报文段 A 才到达服务器,服务器随即返回确认报文并进入ESTABLISHED
状态,但是已经进入CLOSED
状态的客户端无法再接受确认报文段,更无法进入ESTABLISHED
状态,这将导致服务器长时间单方面等待,造成资源浪费。 - 三次握手才能让双方均确认自己和对方的发送和接收能力都正常:
- 第一次握手:客户端只是发送出请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和客户端的发送能力正常。
- 第二次握手:客户端可以确认自己发送能力和接收能力正常,服务器的发送能力和接收能力正常。
- 第三次握手:服务器才可以确认自己的发送能力以及客户端的接受能力正常。
- 同步双方初始序列号:告知对方自己的初始序号值,并确认收到对方的初始序号值。TCP 实现了可靠的数据传输,原因之一就是 TCP 报文段中维护了序号字段和确认序号字段,通过这两个字段双方都可以知道在自己发出的数据中,哪些是已经被对方确认接收的。这两个字段的值会在初始序号值的基础上递增,如果是两次握手,只有发起方的初始序号可以得到确认,而另一方的初始序号则得不到确认,一来一回才能确保双方的初始序列号能被可靠的同步。
3.6 为什么需要三次握手,而不是四次?
因为三次握手已经可以确认双方的发送和接收能力正常,双方都知道彼此已经准备好,而且也可以完成对双方初始序号值的确认,也就无需第四次握手了。
- 第一次握手:服务端确认自己收、对方发报文功能正常。
- 第二次握手:客户端确认自己发、自己收、对方收、对方发报文功能正常,客户端认为连接己建立。
- 第三次握手:服务端确认自己发、对方收报文功能正常,此时双方均建立连接,可以正常通信。
3.7 三次握手时如果客户端第三次发送的确认包丢失了会怎样?
客户端收到服务器的 SYN-ACK 报文后,就会给服务器回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH
状态。
因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务器那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。第二次握手的最大重传次数由 tcp_synack_retries
参数控制。
注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
3.8 客户端发送的第一个 SYN 报文,服务器没有收到怎么办?
当客户端想和服务器建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT
状态。如果客户端迟迟收不到服务器的 SYN-ACK 报文(第二次握手),就会触发超时重传机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。
不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。
当客户端在 1 秒后没收到服务器的 SYN-ACK 报文后,客户端就会重发 SYN 报文,在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries
内核参数控制,这个参数是可以自定义的,默认值一般是 5。
通常,第一次超时重传是在 1 秒后,第二次为 2 秒,第三次为 4 秒,即每次超时的时间设定是上一次的 2 倍。
3.9 第一次握手过程中服务器内部做了哪些工作?
服务器收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN + ACK,接着客户端会返回 ACK,服务器收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 Accept 队列,等待进程调用 accept()
函数时把连接取出来。
3.10 什么是 SYN 洪泛攻击?如何防范?
SYN 洪泛攻击属于 DOS 攻击的一种,它利用 TCP 协议缺陷,通过发送大量的半连接请求,耗费 CPU 和内存资源。
原理:
- 在三次握手过程中,服务器发送
[SYN/ACK]
包(即第二个包)之后、收到客户端的[ACK]
包(即第三个包)之前的 TCP 连接称为半连接(half-open connect),此时服务器处于SYN_RECV
(等待客户端响应)状态。如果接收到客户端的[ACK]
,则 TCP 连接成功,如果未接收到,则会不断重发请求直至成功。 - SYN 攻击的攻击者在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送
[SYN]
包,服务器回复[SYN/ACK]
包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时。 - 这些伪造的
[SYN]
包将长时间占用未连接队列,影响了正常的 SYN,导致目标系统运行缓慢、网络堵塞甚至系统瘫痪。
检测:当在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。
防范:
- 通过防火墙、路由器等过滤网关防护。
- 通过加固 TCP/IP 协议栈防范,如增加最大半连接数,缩短超时时间。
- SYN Cookies 技术。SYN Cookies 是对 TCP 服务器端的三次握手做一些修改,专门用来防范 SYN 洪泛攻击的一种手段。
3.11 TCP 的四次挥手过程是什么?
- 第一次挥手:客户端向服务器发送连接释放报文(
FIN = 1, ACK = 1
),主动关闭连接,同时等待服务器的确认,客户端进入FIN_WAIT_1
(终止等待 1)状态。序列号seq = u
,为客户端上次发送的报文的最后一个字节的序号 + 1。 - 第二次挥手:服务器收到 FIN 报文后,立即发出确认报文(
ACK = 1
),序列号seq = v
,为服务器上次发送的报文的最后一个字节的序号 + 1,确认号ack = u + 1
,服务器进入CLOSE_WAIT
(关闭等待)状态。在收到 FIN 报文的时候,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,服务器应用程序可以通过read
调用来感知这个 FIN 包,这个 EOF 会被放在已排队等候的其他已接收的数据之后,所以必须得继续read
接收缓冲区已接收的数据。此时 TCP 连接处于半关闭状态,即客户端到服务器的连接已经释放了,但是服务器到客户端的连接还未释放。这表示客户端已经没有数据发送了,但是服务器可能还要给客户端发送数据。 - 第三次挥手:客户端收到服务器的确认后进入
FIN_WAIT_2
(终止等待 2)状态,等待服务器发出连接释放报文段。服务器向客户端发送连接释放报文(FIN = 1, ACK = 1
),主动关闭连接,同时等待客户端的确认,服务器进入LAST_ACK
(最后确认)状态。- 序列号
seq = w
,即服务器上次发送的报文的最后一个字节的序号 + 1,可能在半关闭状态服务器又发送了一些数据。 - 确认号
ack = u + 1
,与第二次挥手相同,因为这段时间客户端没有发送数据。
- 序列号
- 第四次挥手:客户端收到服务器的连接释放报文后,立即发出确认报文(
ACK = 1
),序列号seq = u + 1
,确认号为ack = w + 1
。此时,客户端就进入了TIME_WAIT
(时间等待)状态。注意此时客户端的 TCP 连接还没有释放,必须经过 2 * MSL(最长报文段寿命)的时间后,才进入CLOSED
状态。而服务器只要收到客户端发出的确认,就立即进入CLOSED
状态。可以看到,服务器结束 TCP 连接的时间要比客户端早一些。
3.12 为什么连接的时候是三次握手,关闭的时候却是四次挥手?
服务器在收到客户端的 FIN 报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会马上对客户端的 FIN 报文段做出应答,返回 ACK 报文段。接下来可能会继续发送数据,在数据发送完后,服务器会向客户端发送 FIN 报文,表示数据已经发送完毕,请求关团连接。
关键点在于发送第三次挥手的控制权不在内核,而是在被动关闭方(前面所提到的服务器)的应用程序,因为应用程序可能还有数据要发送,由应用程序决定什么时候调用关闭连接的函数,当调用了关闭连接的函数,内核就会发送 FIN 报文了,服务器的 ACK 和 FIN 一般都会分开发送,从而导致多了一次,这也就是为什么四次挥手的中间两次不能合并为一次,一共需要四次挥手。
3.13 如果第三次挥手一直没发,会发生什么?
当主动方收到 ACK 报文后,会处于 FIN_WAIT2
状态,就表示主动方的发送通道已经关闭,接下来将等待对方发送 FIN 报文,关闭对方的发送通道。
这时,如果连接是用 shutdown
函数关闭的,连接可以一直处于 FIN_WAIT2
状态,因为它可能还可以发送或接收数据。但对于 close
函数关闭的孤儿连接,由于无法再发送和接收数据,所以这个状态不可以持续太久,而 tcp_fin_timeout
参数控制了这个状态下连接的持续时长,默认值是 60 秒,意味着对于孤儿连接(调用 close
关闭的连接),如果在 60 秒后还没有收到 FIN 报文,连接就会直接关闭。
3.14 主动断开连接时若客户端 FIN 包丢失,服务器的状态是什么?
当客户端(主动关闭方)调用 close
函数后,就会向服务器发送 FIN 报文,试图与服务器断开连接,此时客户端的连接进入到 FIN_WAIT_1
状态。正常情况下,如果能及时收到服务器(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2
状态。
如果第一次挥手丢失了,那么客户端迟迟收不到服务器的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries
参数控制。
当客户端重传 FIN 报文的次数超过 tcp_orphan_retries
后,就不再发送 FIN 报文,会再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么客户端直接进入到 CLOSED
状态,而服务器还是 ESTABLISHED
状态。
3.15 为什么四次挥手后客户端的 TIME_WAIT 状态必须等待 2MSL?
MSL(Maximum Segment Lifetime,报文最大生存时间)是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个路由器此值就减 1,当值为 0 时数据报将被丢弃,同时发送 ICMP 报文通知源主机。
MSL 与 TTL 的区别:MSL 的单位是时间,而 TTL 是经过的路由跳数。所以 MSL 应该要大于等于 TTL 消耗至 0 的时间,以确保报文已被自然消亡。
TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
详细地说主要有以下两个原因:
- 确保最后一个 ACK 报文段能够到达服务器,从而使服务器正常关闭连接。第四次挥手时,客户端第四次挥手的 ACK 报文段不一定会到达服务器。服务器会超时重传 FIN-ACK 报文段,此时如果客户端已经断开了连接,那么就无法响应服务端的二次请求,这样服务器迟迟收不到 FIN-ACK 报文段的确认,就无法正常断开连接。客户端等待 2MSL 时间即客户端 ACK 报文段 1MSL 超时 + 服务器 FIN-ACK 报文段 1MSL 传输,就能够收到服务器重传的 FIN-ACK 报文段,然后客户端重传一次 ACK 报文段,并重新启动 2MSL 计时器。如此保证服务器能够正常关闭。如果服务器重发的 FIN-ACK 报文段没有成功地在 2MSL 时间里传给客户端,服务器则会继续超时重试直到断开连接。
- 防止已失效的连接请求报文段出现在之后的连接中。TCP 要求在 2MSL 内不使用相同的序列号。客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以保证本连接持续的时间内产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。或者即使收到这些过时的报文,也可以不处理它。
3.16 如果已经建立了连接,但是客户端出现故障了怎么办?
通过定时器 + 超时重试机制,尝试获取确认,直到最后会自动断开连接。
具体而言,TCP 设有一个保活计时器。服务器每收到一次客户端的数据,都会重新设置这个计时器,时间通常是设置为两小时,若两小时还没有收到客户端的任何数据,服务器就发送一个探测报文段,之后则每隔 75 秒发送一次,若一连发送 10 个探测报文段后客户端依然没有响应,那么服务器就认为客户端出现故障,接着就关闭这个连接。
3.17 TIME_WAIT 是服务器还是客户端的状态?
TIME_WAIT
是主动断开连接的一方会进入的状态,一般情况下,都是客户端所处的状态,服务器端一般设置不主动关闭连接。
TIME_WAIT
需要等待 2MSL,在大量短连接的情况下,TIME_WAIT
会太多,这也会消耗很多系统资源。对于服务器来说,在 HTTP 协议里指定 KeepAlive(浏览器重用一个 TCP 连接来处理多个 HTTP 请求),由浏览器来主动断开连接,可以一定程度上减少服务器的这个问题。
3.18 TCP 和 UDP 的区别是什么?
- 连接:TCP 是面向连接的传输层协议,传输数据前先要建立连接。UDP 不需要连接,即刻传输数据。
- 服务对象:TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信。
- 可靠性:TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议。
- 拥塞控制、流量控制:TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
- 首部开销:TCP 首部长度较长,会有一定的开销,首部在没有使用“选项”字段时是 20 个字节。UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
- 传输方式:TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
3.19 TCP 协议如何保证可靠性,即如何实现可靠传输?
TCP 主要提供了检验和、序列号/确认应答、超时重传、滑动窗口、拥塞控制和流量控制等方法实现了可靠性传输。
- 连接管理:即三次握手和四次挥手。连接管理机制能够建立起可靠的连接,这是保证传输可靠性的前提。
- 检验和:通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃 TCP 报文段,重新发送。
- 序列号/确认应答:序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。TCP 传输的过程中,每次接收方收到数据后,都会对发送方进行确认应答。也就是发送 ACK 报文段,这个 ACK 报文段当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发,若指定时间内发送方仍未收到确认应答,就会启动超时重传。
- 滑动窗口:滑动窗口既提高了报文传输的效率,也避免了发送方发送过多的数据而导致接收方无法正常处理的异常。
- 超时重传:超时重传的时间是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间会被认为是丢包了,需要重传。最大超时时间是动态计算的。
- 拥塞控制:在数据传输过程中,可能由于网络状态的问题,造成网络拥堵,此时引入拥塞控制机制,当网络拥堵严重时,发送端减少数据发送,在保证 TCP 可靠性的同时,提高性能。拥塞控制是通过发送端维护一个拥塞窗口来实现的,发送端的发送速度受限于滑动窗口和拥塞窗口中的最小值。拥塞控制方法分为:慢开始,拥塞避免、快重传和快恢复。
- 流量控制:如果主机 A 一直向主机 B 发送数据,不考虑主机 B 的接收能力,则可能导致主机 B 的接收缓冲区满了而无法再接收数据,从而会导致大量的数据丢包,引发重传机制。而在重传的过程中,若主机 B 的接收缓冲区情况仍未好转,则会将大量的时间浪费在重传数据上,降低传送数据的效率。所以引入流量控制机制,TCP 支持根据接收端的处理能力,来决定发送端的发送速度,主机 B 通过告诉主机 A 自己接收缓冲区的大小,来使主机 A 控制发送的数据量。流量控制与 TCP 协议报头中的窗口大小有关。
3.20 详细讲一下 TCP 的滑动窗口?
在进行数据传输时,如果传输的数据比较大,就需要拆分为多个数据包进行发送。TCP 协议需要对数据进行确认后,才可以发送下一个数据包。这样一来,就会在等待确认应答包环节浪费时间。
为了避免这种情况,TCP 引入了窗口概念。窗口大小指的是不需要等待确认应答包而可以继续发送数据包的最大值。
滑动窗口里面也分为有三种类型的数据,第一种是已经发送且收到确认但是未按序到达,即没有在窗口尾部形成一段连续的序列;第二种是已经发送但是未被确认的数据;第三种是等待发送的数据。随着已发送的数据不断被确认,窗口内等待发送的数据也会不断被发送。整个窗口就会不断往前移动,让还没轮到的数据进入窗口内。
可以看到滑动窗口起到了一个限流的作用,也就是说当前滑动窗口的大小决定了当前 TCP 发送包的速率,而滑动窗口的大小取决于拥塞控制窗口和流量控制窗口的两者间的最小值。
3.21 详细讲一下拥塞控制?
TCP 一共使用了四种算法来实现拥塞控制:
- 慢开始(slow-start)
- 拥塞避免(congestion avoidance)
- 快重传(fast retransmit)
- 快恢复(fast recovery)
发送方维持一个叫做拥塞窗口 cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。
- 慢开始:不要一开始就发送大量的数据,由小到大逐渐增加拥塞窗口的大小。
例如一开始发送方先设置 cwnd = 1,发送第一个报文段,等发送方接收到对方的确认后把 cwnd 从 1 增大到 2。此后每经过一个传输轮次,拥塞窗口 cwnd 就加倍。
为了防止拥塞窗口 cwnd 增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh
状态变量。- 当
cwnd < ssthresh
时,使用慢开始算法。 - 当
cwnd > ssthresh
时,停止使用慢开始算法改用拥塞避免算法。 - 当
cwnd = ssthresh
时,即可使用慢开始算法,也可使用拥塞避免算法。
- 当
- 拥塞避免:拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加一而不是加倍。这样拥塞窗口按线性规律缓慢增长。
- 快重传:我们可以剔除一些不必要的拥塞报文,提高网络吞吐量。比如接收方在收到一个失序的报文段后就立即发出重复确认,而不要等到自己发送数据时捎带确认。快重传规定:发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。
- 快恢复:主要是配合快重传,当发送方连续收到三个重复确认时,就执行乘法减小算法,把
ssthresh
门限减半(为了预防网络发生拥塞),但接下来并不执行慢开始算法,因为如果网络出现拥塞的话就不会收到好几个重复的确认,收到三个重复确认说明网络状况还可以。
4. 网络场景
4.1 描述一下打开百度首页后发生的网络过程?
- 解析 URL:分析 URL 所需要使用的传输协议和请求的资源路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,若存在则对非法字符进行转义后再进行下一过程。
- 缓存判断:浏览器缓存 → 系统缓存(hosts 文件)→ 路由器缓存 → ISP 的 DNS 缓存,如果其中某个缓存存在,直接返回服务器的 IP 地址。
- DNS 解析:如果缓存未命中,浏览器向本地 DNS 服务器发起请求,最终可能通过根域名服务器、顶级域名服务器、权威域名服务器逐级查询,直到获取目标域名的 IP 地址。
- 获取 MAC 地址:当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 地址与本机的子网掩码相结合,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 ARP 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。
- 建立 TCP 连接:主机将使用目标 IP 地址和目标 MAC 地址发送一个 TCP SYN 包,请求建立一个 TCP 连接,然后交给路由器转发,等路由器转到目标服务器后,服务器回复一个 SYN-ACK 包,确认连接请求。最后,主机发送一个 ACK 包,确认已收到服务器的确认,TCP 连接建立完成。
- HTTPS 的 TLS 四次握手:如果使用的是 HTTPS 协议,在通信前还存在 TLS 的四次握手。
- 发送 HTTP 请求:连接建立后,浏览器会向服务器发送 HTTP 请求。请求中包含了用户需要获取的资源的信息,例如网页的 URL、请求方法(如 GET、POST)等。
- 服务器处理请求并返回响应:服务器收到请求后,会根据请求的内容进行相应的处理。例如,如果是请求网页,服务器会读取相应的网页文件,并生成 HTTP 响应。
4.2 网页非常慢转圈圈的时候,要定位问题需要从哪些角度?
最直接的办法就是抓包,排查的思路大概有:
- 先确定是服务器的问题,还是客户端的问题。先确认浏览器是否可以访问其他网站,如果不可以,说明客户端网络自身的问题,然后检查客户端网络配置(连接 WIFI 正不正常,有没有插网线)。如果可以正常浏览其他网页,说明客户端网络是可以正常上网的。
- 如果客户端网络没问题,就抓包确认 DNS 是否解析出了 IP 地址,如果没有解析出来,说明域名写错了,如果解析出了 IP 地址,抓包确认有没有和服务器建立三次握手,如果能成功建立三次握手,并且发出了 HTTP 请求,但是就是没有显示页面,可以查看服务器返回的响应码:
- 如果是 404 错误码,检查输入的 URL 是否正确;
- 如果是 500,说明服务器此时有问题;
- 如果是 200,F12 看看前端代码是否有问题导致浏览器没有渲染出页面。
- 如果客户端网络是正常的,但是访问速度很慢,导致很久才显示出来。这时候要看客户端的网口流量是否太大了,导致 TCP 发生丢包之类的问题。
总之就是一层一层排查,有没有插网线、网络配置是否正确、DNS 有没有解析出 IP 地址、TCP 有没有三次握手、HTTP 返回的响应码是什么等等。
5. 网络攻击
5.1 什么是 DDoS 攻击?怎么防范?
分布式拒绝服务(DDoS)攻击是通过大规模互联网流量淹没目标服务器或其周边基础设施,以破坏目标服务器、服务或网络正常流量的恶意行为。
DDoS 攻击是通过连接互联网的计算机网络进行的。这些网络由计算机和其他设备(例如 IoT 设备)组成,它们感染了恶意软件,从而被攻击者远程控制。这些个体设备称为机器人(或僵尸),一组机器人则称为僵尸网络。
一旦建立了僵尸网络,攻击者就可通过向每个机器人发送远程指令来发动攻击。当僵尸网络将受害者的服务器或网络作为目标时,每个机器人会将请求发送到目标的 IP 地址,这可能导致服务器或网络不堪重负,从而造成对正常流量的拒绝服务。由于每个机器人都是合法的互联网设备,因而可能很难区分攻击流量与正常流量。
常见的 DDoS 攻击包括以下几类:
- 网络层攻击:比较典型的攻击类型是 UDP 反射攻击,例如:NTP Flood 攻击,这类攻击主要利用大流量拥塞被攻击者的网络带宽,导致被攻击者的业务无法正常响应客户访问。
- 传输层攻击:比较典型的攻击类型包括 SYN Flood 攻击、连接数攻击等,这类攻击通过占用服务器的连接池资源从而达到拒绝服务的目的。
- 会话层攻击:比较典型的攻击类型是 SSL 连接攻击,这类攻击占用服务器的 SSL 会话资源从而达到拒绝服务的目的。
- 应用层攻击:比较典型的攻击类型包括 DNS Flood 攻击、HTTP Flood 攻击、游戏假人攻击等,这类攻击占用服务器的应用处理资源极大地消耗服务器处理性能从而达到拒绝服务的目的。
为了防范 DDoS 攻击,可以采取以下措施:
- 增强网络基础设施:提升网络带宽、增加服务器的处理能力和承载能力,通过增强基础设施的能力来抵御攻击。
- 使用防火墙和入侵检测系统:配置防火墙规则,限制不必要的网络流量,阻止来自可疑 IP 地址的流量。入侵检测系统可以帮助及时发现并响应 DDoS 攻击。
- 流量清洗和负载均衡:使用专业的 DDoS 防护服务提供商,通过流量清洗技术过滤掉恶意流量,将合法流量转发给目标服务器。负载均衡可以将流量均匀地分发到多台服务器上,减轻单一服务器的压力。
- 配置访问控制策略:限制特定 IP 地址或 IP 段的访问,设置访问频率限制,防止过多请求集中在单个 IP 上。
5.2 CSRF 攻击是什么?
CSRF(跨站请求伪造)是一种攻击手段,攻击者通过诱导用户执行恶意操作,从而获取用户数据或执行恶意代码。CSRF 攻击通常通过伪造一个合法的 HTTP 请求来实现,这个请求看起来是合法的,但实际上是为了执行一个攻击者控制的操作。
解决 CSRF 攻击的方法主要有以下几种:
- 验证用户会话:在服务器端对用户会话进行验证,确保请求的会话标识符与当前会话标识符匹配。这样可以防止攻击者伪造会话标识符。
- 使用双重验证:除了会话验证,还可以使用其他验证方式,例如验证码、签名验证等。这些验证方式可以增加攻击的难度。
- 防止跨站请求:通过设置 CSP(内容安全策略)来防止跨站请求,限制网页中可执行的脚本源,减少攻击者诱导用户执行恶意操作的可能性。
- 避免使用自动提交表单:禁用默认的自动提交功能,要求用户在提交表单前确认操作,防止攻击者诱导用户在未经授权的情况下提交表单。
- 强制 Referer 头部:在服务器端检查请求的 Referer 头部,确保请求来自可信来源。
5.3 XSS 攻击是什么?
XSS 是跨站脚本攻击,攻击者通过在 Web 页面中插入恶意脚本代码,然后诱使用户访问该页面,从而使得恶意脚本在用户浏览器中执行,从而盗取用户信息、会话信息等敏感数据,甚至控制用户账户。
XSS 攻击可以分为 3 类:存储型(持久型)、反射型(非持久型)、DOM 型:
- 存储型 XSS:注入型脚本永久存储在目标服务器上,当浏览器请求数据时,脚本从服务器上传回并执行。
- 反射型 XSS:当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web 服务器将注入脚本,比如一个错误信息,搜索结果等返回到用户的浏览器上。由于浏览器认为这个响应来自“可信任”的服务器,所以会执行这段脚本。
- 基于 DOM 的 XSS:通过修改原始的客户端代码,受害者浏览器的 DOM 环境改变,导致有效载荷的执行。也就是说,页面本身并没有变化,但由于 DOM 环境被恶意修改,有客户端代码被包含进了页面,并且意外执行。
预防 XSS 攻击的方法主要包括以下几点:
- 输入验证:对所有用户输入的数据进行有效性检验,过滤或转义特殊字符。例如,禁止用户输入 HTML 标签和 JavaScript 代码。
- 输出编码:在网页输出用户输入内容时,使用合适的编码方式,如 HTML 转义、URL 编码等,防止恶意脚本注入。
- Content Security Policy(CSP):通过设置 CSP 策略,限制网页中可执行的脚本源,有效防范 XSS 攻击。
- 使用 HttpOnly 标记:在设置 Cookie 时,设置 HttpOnly 属性,使得 Cookie 无法被 JavaScript 代码读取,减少受到 XSS 攻击的可能。
5.4 了解过 DNS 劫持吗?
DNS 劫持的原理是攻击者在用户查询 DNS 服务器时篡改响应,将用户请求的域名映射到攻击者控制的虚假 IP 地址上,使用户误以为访问的是正常网站,实际上被重定向到攻击者操控的恶意网站。这种劫持可以通过植入恶意的 DNS 记录或劫持用户的 DNS 流量来实现。