Contents

计算机网络

计算机网络

Contents

一、TCP 与 UDP

问:TCP 与 UDP 的区别⭐

  1. 是否面向连接:UDP 在传送数据之前不需要先建立连接;而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
  2. 是否是可靠传输:UDP 提供不可靠的传输服务,收到报文无需确认,不保证数据不丢失,不保证按序到达;TCP 提供可靠的传输服务,TCP 通过三次握手和四次挥手来建立和释放连接,数据传递时,有确认、窗口、重传、拥塞控制机制,确保传输的数据,无差错、不丢失、不重复、并且按序到达。
  3. 传输形式:TCP 是面向字节流的,UDP 是面向报文的。
  4. 是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多。
  5. 首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
  6. 传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
image-20230510154451229 image-20230510154508784

问:什么时候选择 TCP,什么时候选 UDP?

UDP 一般用于即时通信,比如:语音、视频、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。

TCP 用于对传输准确性要求特别高的场景,比如文件传输、发送和接收邮件、远程登录等等。

问:HTTP 基于 TCP 还是 UDP?

HTTP 3.0 之前是基于 TCP 协议的,而 HTTP3.0 将弃用 TCP,改用 基于 UDP 的 QUIC 协议 。此变化主要为了解决 HTTP/2 中存在的队头阻塞问题。由于 HTTP/2 在单个 TCP 连接上使用了多路复用,受到 TCP 拥塞控制的影响,少量的丢包就可能导致整个 TCP 连接上的所有流被阻塞。

问:基于 TCP、UDP 的协议?

  1. ==基于 TCP 的协议==

HTTP 协议:超文本传输协议(HTTP,HyperText Transfer Protocol)主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。

HTTPS 协议:更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议

FTP 协议:文件传输协议 FTP(File Transfer Protocol),提供文件传输服务,基于 TCP 实现可靠的传输。使用 FTP 传输文件的好处是可以屏蔽操作系统和文件存储方式。

SMTP 协议:简单邮件传输协议(SMTP,Simple Mail Transfer Protocol)的缩写,基于 TCP 协议,用来发送电子邮件。注意⚠️:接受邮件的协议不是 SMTP 而是 POP3 协议。

POP3/IMAP 协议:POP3 和 IMAP 两者都是负责邮件接收的协议。

Telnet 协议:远程登陆协议,通过一个终端登陆到其他服务器。被一种称为 SSH 的非常安全的协议所取代。

SSH 协议:SSH(Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。

  1. ==基于 UDP 的协议==

DHCP 协议:动态主机配置协议,动态配置 IP 地址;

DNS域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上 DNS 同时支持 UDP 和 TCP 协议。

问:如何理解 TCP 是面向字节流的?

之所以会说 TCP 是面向字节流的协议,UDP 是面向报文的协议,是因为操作系统对 TCP 和 UDP 协议的发送方的机制不同

  1. 为什么 UDP 是面向报文的?

当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分,在组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界,这样接收方在接收到 UDP 报文后,读一个 UDP 报文就能读取到完整的用户消息。

  1. 为什么 TCP 是面向字节流的?

当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个 TCP 报文,也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输。

这时,接收方的程序如果不知道发送方发送的消息的长度,也就是不知道消息的边界时,是无法读出一个有效的用户消息的,因为用户消息被拆分成多个 TCP 报文后,并不能像 UDP 那样,一个 UDP 报文就能代表一个完整的用户消息。

例如,发送方准备发送 「Hi.」和「I am Xiaolin」这两个消息。

在发送端,当我们调用 send 函数完成数据“发送”以后,数据并没有被真正从网络上发送出去,只是从应用程序拷贝到了操作系统内核协议栈中。至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件。也就是说,我们不能认为每次 send 调用发送的数据,都会作为一个整体完整地消息被发送出去。

实际发送有如下可能的情况:

image-20230526155446910 image-20230526155459255 image-20230526155512401

因此,我们不能认为一个用户消息对应一个 TCP 报文,正因为这样,所以 TCP 是面向字节流的协议

当两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP 粘包问题,这时接收方不知道消息的边界的话,是无法读出有效的消息。

要解决这个问题,要交给应用程序

问:TCP 的粘包问题是啥?咋解决呢?UDP 有吗?

TCP 粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。其实就是消息边界模糊造成的。

产生原因:

  • 由TCP连接复用造成的粘包问题。
  • 因为TCP默认会使用 Nagle 算法,此算法会导致粘包问题:合并相连的小数据包,再一次性发送,以达到提升网络传输效率的目的。但接收方又不知道你合并了。
  • 接收方来不及接收缓冲区的包,导致缓冲区有多个包,而一次性读取时,就发生了粘包

解决问题:tcp 协议的包头有 20 字节,ip 协议的包头也有 20 字节,如果仅仅传输 1 字节的数据,在网络上传输的就有 20 + 20 + 1 = 41 字节,其中真正有用的数据只有 1 个字节,这对效率和带宽是极大的浪费。

Nagle 算法:如果是连续的小数据包,大小没有一个 MSS(Maximum Segment Size,最大分段大小),并且还没有收到之前发送的数据包的 Ack 信息,那么这些小数据包就会在发送端暂存起来,直到小数据包累积到一个 MSS,或者收到一个 Ack 为止。

这个算法虽然减少了不必要的网络传输,但既导致了延迟,也导致了粘包

解决方法:

  • 以指定字符串为包结束标志,比如\n等,比如HTTP;
image-20230526155723333
  • 添加一个自定义的包头,记录本包的数据长度,然后自己写个buffer缓冲一下,等收到指定长度了再交付处理;(你不就是用的这个吗?hhh)

比如,固定前 8 个字节,定义为数据长度,真正的数据在后面。

  • 固定每个包的长度(太不灵活了)。

UDP 是面向报文的(具有长度字段),具有消息边界,每个包都是独立的,因此不会。而TCP首部里是没有长度这个信息的,TCP 发送端在发的时候就不保证发的是一个完整的数据报,仅仅看成一连串无结构的字节流,这串字节流在接收端收到时哪怕知道长度也没用,因为它很可能只是某个完整消息的一部分

二、TCP 三次握手和四次挥手

问:TCP 三次握手和四次挥手⭐⭐⭐🚩

==三报文握手:==

  1. 首先,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
  2. 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态;
  3. 服务端收到客户端的 SYN 报文后,首先服务端随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYNACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
  4. 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。
  5. 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
image-20230226202943082

注意

  • TCP 规定携带 SYN 的确认报文段不携带数据,但也要消耗序号;
  • TCP 规定普通 TCP 确认报文段可以携带数据,若不携带数据,则不消耗序号。即上图传输的第一个数据报文段序号seq=x+1。即第三次握手是可以携带数据的,前两次握手是不可以携带数据的

==四报文挥手:==

image-20230226204232488

问:用什么数据结构管理半连接队列和全连接队列?

image-20230404170431052

问:全连接队列和半连接队列满了分别会发生什么?

  • 全连接队列满了之后,再收到新的连接,就会丢弃;
  • 半连接队列满了之后,再收到新的连接,也会丢弃。

但半连接队列满了,不意味着无法建立连接了。通过开启syncookies,就可以在不使用 SYN 半连接队列的情况下成功建立连接

syncookies 是这么做的:服务器根据当前状态计算出一个值,放在己方发出的 SYN+ACK 报文中发出,当客户端返回 ACK 报文时,取出该值验证,如果合法,就认为连接建立成功:

image-20230424112742698

问:TCP 连接建立解决的问题?

解决了以下三个问题:

  1. 使 TCP 双方能够确认对方的存在
  2. 使 TCP 双方能够协商一些参数(如:最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等);
  3. 使 TCP 双方能够对运输实体资源(如:缓存大小、连接表中的项目等)进行分配。

问:为什么要三次握手?⭐🚩

从三个方面讲:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因);
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

三次握手的首要原因是为了防止旧的重复连接初始化造成混乱

如果是两次握手连接,就无法阻止历史连接

image-20230226203536937

**避免过期连接的建立。**若为两次握手,客户端先第一次握手,然而阻塞了,然后重新发起第一次握手,此时第一次握手数据报已经到了,然后服务端第二次握手,连接就建立了,就可以发数据了,但是客户端会通通丢弃!!!造成资源浪费!!!

三次握手第二主要的目的就是双方确认自己与对方的发送与接收是正常的,并同步双方的初始序列号。若为两次,则只有一方能确定对方的序列号:

  1. 第一次握手 :Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
  2. 第二次握手Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
  3. 第三次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

问:每次建立 TCP 连接时,初始化的序列号有啥讲究?⭐🚩

每次初始化的序列号都要求不一样,主要有以下原因:

  • 为了防止历史报文被下一个相同四元组的连接接收(主要方面);
  • 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。
image-20230519142712288

客户端和服务端建立一个 TCP 连接,在客户端发送数据包被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。

紧接着,客户端又与服务端建立了与上一个连接相同四元组的连接;

在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。

可以看到,如果每次建立连接,客户端和服务端的初始化序列号都是一样的话,很容易出现历史报文被下一个相同四元组的连接接收的问题。如果每次建立连接客户端和服务端的初始化序列号都**「不一样」**,就有大概率因为历史报文的序列号「不在」对方接收窗口,从而很大程度上避免了历史报文。

问:初始序列号是如何随机产生的?⭐

起始 ISN 是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。

RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

  • M 是一个计时器,这个计时器每隔 4 微秒加 1。
  • F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。

问:为什么要四次挥手?

因为 TCP 是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

举个例子:A 和 B 打电话,通话即将结束后。

  1. 第一次挥手 :A 说“我没啥要说的了”
  2. 第二次挥手 :B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话
  3. 第三次挥手 :于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
  4. 第四次挥手 :A 回答“知道了”,这样通话才算结束。

问:四次挥手中,最后客户端要等待2MSL,有必要吗?为什么不直接关闭呢?

MSL:报文最大生存时间

MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。2MSL 就相当于一来一回的最大时间。

==回答:==

有必要。如果客户端最后一次ACK报文段丢失了,服务端就会重发 FIN,如果客户端在 2MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。

等待 2MSL 是为了保证本次连接中产生的所有报文段都从网络中消失,避免影响之后的 TCP 连接。

image-20230226204454859

三、TCP 传输可靠性

问:TCP 如何保证数据传输可靠性?⭐

  1. TCP 基于以字节为单位的滑动窗口来实现可靠传输;
  2. TCP 拥有校验和机制。如果收到报文段的检验和有差错,TCP 将丢弃这个报文段;
  3. TCP 通过序号机制保证数据的按序交付。会将不按序到达的数据放在接收窗口,待接收完毕再按序交付给应用层;
  4. TCP 要求接收方必须有累计确认捎带确认机制;
  5. TCP 拥有超时重传机制。会对发送的报文段启动定时器,未接收到接收端发来的确认报文会导致定时器超时,从而重新发送该报文段;
  6. 流量控制接收端根据自身情况,当来不及处理接收到的数据时,降低发送端的发送速率,防止包丢失。
  7. 拥塞控制。网络拥塞时,缩减发送端的拥塞窗口,降低数据发送速率,防止包丢失。

问:TCP 如何实现流量控制?⭐

接收方通过 TCP 头窗口字段告知发送方本方可接收的最大数据量,用以解决发送速率过快导致接收方不能接收的问题,所以流量控制是点对点控制。

过程自己想想说叭。

问:流量控制中,死锁的局面?

image-20230226212529706

问:TCP 拥塞控制如何实现?⭐🚩

为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送窗口 = min(拥塞窗口, 接收窗口)

TCP 的拥塞控制采用了四种算法,即慢开始拥塞避免快重传快恢复

  • 慢开始:cwnd 初始值为 1,还未到慢开始门限时,每经过一个传播轮次,cwnd 加倍
  • 拥塞避免:到达慢开始门限时,cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送方的cwnd加 1。当重传计时器超时,很大概率发生拥塞:
    • 慢开始门限 = 拥塞时的cwnd / 2
    • cwnd = 1,重新开始慢开始算法。

但是,个别报文段的丢失也会引起计时器超时,但是此时网络可能并没有拥塞,如果从慢开始再次开始,就降低了网络效率。==针对这种情况,新产生了快重传快恢复。==

  • 快重传:接收方不要等到自己有数据发送时才进行捎带确认,而是要立即发送确认;发送方一旦收到3个重复确认,就对相应报文段立即重传不要等超时计时器超时
  • 快恢复:一旦收到3个重复确认,就知道只是丢失了个别的报文段,于是执行快恢复算法:将cwnd = 拥塞时的cwnd / 2慢开始门限 = 拥塞时的cwnd / 2
image-20230226214334479

问:流量控制与拥塞控制的区别?

  • 拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。
  • 相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,确保接收端来得及接收。

四、HTTP 和 HTTPS

4.1 基础概念

HTTP 协议,全称超文本传输协议(Hypertext Transfer Protocol),也就是传输网络上的包括文本在内的各式各样的消息。HTTP 是一个无状态(stateless)协议,也就是说服务器不维护任何有关客户端过去所发请求的消息。HTTP 是应用层协议,它以 TCP(传输层)作为底层协议。默认端口为 80

HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证。默认端口号是 443

HTTPS 协议中,SSL 通道通常使用基于密钥的加密算法,密钥长度通常是 40 比特或 128 比特。

问:HTTP 的报文格式?

HTTP 有两类报文:请求报文响应报文

HTTP 请求/响应报文由以下内容组成:

  • 请求头
  • HTTP 头部字段
  • 空行
  • 可选的 HTTP 报文主体数据
  1. 请求报文
HTTP 请求报文结构

HTTP 的请求报文分为三个部分:

  • 请求行
    • 请求方法
    • 请求地址 URL
    • HTTP 协议版本
1
GET /index.html HTTP/1.1
  • 请求头(首部行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Accept: */*	
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Content-Length: 21429
Content-Type: application/json
Host: api.github.com
Origin: https://github.com
Referer: https://github.com/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
请求头 说明
Accept 表示浏览器接受的数据类型
Accept-Encoding 表示浏览器接受的数据压缩格式
Host 表示当前请求访问的目标地址
Authorization 表示用户身份认证信息
User-Agent 表示浏览器类型
If-Modified-Since 表示当前请求资源最近一次更新时间
If-None-Match 表示当前请求资源最近一次标识的 ETag 值
Cookie 表示浏览器保存的 Cookie 信息
Referer 表示标识请求引用自哪个地址
  • 消息体

请求体是 POST 请求方式中的请求参数,以 key = value 形式进行存储,多个请求参数之间用 & 连接,如果请求当中请求体,那么在请求头当中的 Content-Length 属性记录的就是该请求体的长度。

1
pageNo=0&pageSize=10&orderNum=306735659327926273&customerMobile=15626000000&startTime=2019-02-01%2000:00:00&endTime=2019-02-25%2014:54:20&status=SUCCESS&source=WECHAT_SHOPPING&canteenId=104&refundStatus=REFUNDED&startPayTime=2019-02-01%2000:00:00&endPayTime=2019-02-25%2014:54:47
  1. 响应报文

一个 http 响应报文也由三个部分组成:

  1. 状态行
  • http 协议版本
  • 状态码
  • 状态码的文本描述

标记响应状态。

1
HTTP/1.1 200 OK
  1. 响应头

和请求头部类似,就是两者之间有一些不同的头部字段。

1
2
3
4
5
6
7
8
HTTP/1.0 200 ok
content-type: application/javascript;charset=utf-8
date: Tue, 07 Mar 2017 03:06:14 GMT
sever: Domain Reliability Searver
content-length: 0
x-xss-protection: 1, mode=bloack
x-frame-options: SAMEORIGIN
alt-svc: quic=":443";ma=2592000;v="36,35,34"
名称 作用
Date 表示当前相应资源发送的服务器日期和时间
Last-Modified 表示当前响应资源最后被修改的服务器时间,用于协商缓存
Transfer-Encoding 表示当前响应资源传输实体的编码格式
Set-Cookie 表示设置 Cookie 信息
Location 在重定向中或者创建新资源时使用
Server 表示服务器名称
  1. 消息体

正文内容,一般在响应头中会用 Content-Length 来明确响应体的长度。

问:解释下超文本传输协议?

HTTP 的名字「超文本协议传输」,它可以拆成两个部分:

  • 超文本
  • 传输协议
  1. 传输协议:就是计算机之间传输数据的一种行为规范和约定;
  2. 超文本:文本是文字,但超文本涵盖了文字、图片、视频、链接等,是这些的混合体。

综上所述,HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」

问:HTTP 协议的通信过程?

通信过程主要如下:

  1. 服务器在 80 端口等待客户的请求。
  2. 浏览器发起到服务器的 TCP 连接(创建套接字 Socket)。
  3. 服务器接收来自浏览器的 TCP 连接。
  4. 浏览器(HTTP 客户端)与 Web 服务器(HTTP 服务器)交换 HTTP 消息。
  5. 关闭 TCP 连接。

问:HTTP 常见的状态码?⭐

image-20230316135823000

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 确定要请求的资源所在 Web 服务器文件名构建 HTTP 请求报文
  • 域名解析:发送 HTTP 请求报文之前,会通过 DNS 将域名转换为对应的 IP 地址:首先会检查本地缓存,如果未命中则会向 本地 DNS 服务器发送查询请求,然后递归的查询;
  • 建立 TCP 连接:浏览器根据 IP 地址和端口号,与目标服务器建立 TCP 连接(三次握手),然后对 HTTP 请求报文进行封装,也就是加一层 TCP 报头,TCP 报文的数据部分就是 HTTP 请求报文了;
  • 封装 IP:TCP 报文还需要通过 IP 传输到服务器,所以要在 TCP 报文前加一层 IP 头,指明目的地址和源地址,生成 IP 报文;
  • 封装 MAC:生成 IP 报文后,还需要封装成 MAC 帧。通过 ARP 协议查询要到达目的 IP 的下一跳的 MAC 地址,MAC 头部携带发送方 MAC 地址接收方目标 MAC 地址,用于两点间传输;
  • 通过不断到达新的路由器,进而更新目的 MAC 地址,最终达到目的 IP 地址;
  • 服务器处理 HTTP 请求:服务器将数据包层层验证并上传,最终到达 HTTP 层。接收到 HTTP 请求后,根据请求的方法、路径和参数等信息,进行相应的业务逻辑处理,并返回 HTTP 响应,包括响应行、响应头和响应体等信息;
  • 浏览器解析渲染页面:浏览器根据 HTTP 响应的状态码、内容类型和编码等信息,解析响应体中的 HTML、CSS 和 JavaScript 等资源,并构建 DOM(文档对象模型)树、CSSOM(CSS 对象模型)树和渲染树等结构,最终将页面渲染到屏幕上。

具体过程

image-20230227150214406

问:HTTP 有哪些常见字段?

1
2
3
4
5
6
7
8
9
Host: www.A.com							// 服务器域名
Content-Length: 1000					// 响应长度
Connection: Keep-Alive					// 决定是否要保持长连接

Content-Type: text/html; Charset=utf-8	// 服务端响应时说明 数据格式
Accept: */*								// 客户端请求时说明 支持接收哪些格式

Content-Encoding: gzip					// 服务端响应时说明 解压方法
Accept-Encoding: gzip, deflate			// 客户端请求时说明 支持的压缩方法

问:HTTP 协议如何解决粘包问题?

HTTP 协议是基于 TCP 协议的,因此需要解决粘包问题:

  • 通过设置回车符、换行符作为 HTTP header 的边界;
  • 通过Content-Length字段作为 HTTP body 的边界。

问:GET 和 POST 有啥区别?

RFC 规范中:

  • GET 是从服务器获取资源。是安全且幂等的;
  • POST 是让服务器根据 body 中的指令处理指定资源。是不安全且不幂等的。

但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:

  • 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等。
  • 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。

虽然 POST 用 body 传输数据,而 GET 用 URL 传输,这样数据会在浏览器地址拦容易看到,但是并不能说 GET 不如 POST 安全的。因为 HTTP 传输的内容都是明文的,虽然在浏览器地址拦看不到 POST 提交的 body 数据,但是只要抓个包就都能看到了。所以,要避免传输过程中数据被窃取,就要使用 HTTPS 协议,这样所有 HTTP 的数据都会被加密传输。

4.2 缓存

问:HTTP 缓存技术?

对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,HTTP 有以下实现方法:

  1. 强制缓存:指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在浏览器这边。
image-20230414220330271

强制缓存利用 HTTP 响应头部中的两个字段实现,用来表示资源在客户端缓存的有效期:

  • Cache-Control,是一个相对时间;
  • Expires,是一个绝对时间;

如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires,建议使用 Cache-Control(选项更多):

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
  • 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
  • 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
  1. 协商缓存:通过服务端告知客户端是否可以使用缓存。比如某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源。
  • 当第一次请求静态的资源时,比如一张图片,服务端除了返回图片信息,在响应头里面还有一个 Etag 的字段;
  • 浏览器会缓存图片信息以及这个字段的值;
  • 当下一次再请求这个图片的时候,浏览器发起的请求头里面会有一个 If-None-Match 的字段,并且把缓存的 Etag 的值写进去发给服务端;
  • 服务端比对图片信息是否有变化,如果没有,则返回浏览器一个 304 的状态码,浏览器会继续使用缓存的图片信息。
image-20230504202104752

通过浏览器缓存的方式,可以减少网络传输的数据大小,从而提升页面展示的性能。

问:强制缓存和协商缓存是什么?

  1. 强制缓存:指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存。决定是否使用缓存的主动性在于浏览器;

实现:主要利用两个 HTTP 响应头部字段实现,它们都用来表示资源在客户端缓存的有效期:

  • Cache-Control,是一个相对时间;
  • Expires,是一个绝对时间;

如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。建议使用 Cache-Control,具体的实现流程如下:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
  • 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
  • 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
  1. 协商缓存:服务端告知客户端是否可以使用缓存的方式被称为协商缓存。

实现:可以基于两种头部来实现。

  • 第一种:利用请求头部中的 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

第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。

注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求

当使用 ETag 字段实现的协商缓存的总过程

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识,这个唯一标识的值是根据当前请求的资源生成的;
  • 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:
    • 如果没有过期,则直接使用本地缓存;
    • 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
  • 服务器再次收到请求后,会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较:
    • 如果值相等,则返回 304 Not Modified,不会返回资源
    • 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
  • 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。

4.3 HTTPS

问:HTTPS 协议的通信过程?

  1. 客户端发送https请求,把自身支持的秘钥算法套件(SSL指定版本、加密组件列表)发送给服务器;

  2. 服务器判断自身是否支持该算法套件,如果支持则返回证书信息,否则断开连接;

  3. 客户端解析证书(通过TLS协议来完成),验证证书是否有效。如果异常,则会提示是否安装证书,常见的就是浏览器搜索栏左侧出现“X”告警按钮等。

  4. 如果证书有效、或者是授信安装证书后,开始传送用服务端公钥加密后的私钥

  5. 服务端通过私钥解密加密信息,得到客户端发送来的会话私钥

  6. 进行会话。

问:SSL/TLS 工作原理?

SSL/TLS 的核心要素是公钥加密对称加密。利用公钥加密协商密钥,利用对称加密算法快速加解密数据

其中,为防止中间人攻击公钥由CA颁发。当客户端(浏览器)向服务器发送 HTTPS 请求时,一定要先获取目标服务器的证书,并根据证书上的信息,检验证书的合法性。一旦客户端检测到证书非法,就会发生错误。

问:CA 证书签发过程?

  1. 服务方 S 生成公私钥,然后向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证;
  2. CA 通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等;如信息审核通过,CA 会向申请者签发认证文件-证书证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构 CA的信息、有效时间、证书序列号等信息的明文,同时包含一个CA的签名签名的产生算法:首先,使用散列函数计算公开的明文信息的信息摘要,然后,采用 CA 的私钥对信息摘要进行加密,密文即签名;
  3. 客户端 C 向服务器 S 发出请求时,S 返回证书文件;
  4. 客户端 C 对证书进行验签:读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应 CA 的公钥解密签名数据,对比证书的信息摘要,如果一致,则可以确认证书的合法性,即公钥合法;
  5. 客户端然后验证证书相关的域名信息、有效时间等信息;
  6. 客户端会内置信任 CA 的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA 的证书,证书也会被判定非法。

五、HTTP 各版本

基础

问:HTTP 1.0 和 HTTP 1.1 区别?

  • 连接方式:HTTP 1.0 为非持续连接,每次浏览器要请求一个文件都要与服务器建立TCP链接,当收到响应后就立即关闭连接;HTTP 1.1 支持持续连接,浏览器收到响应后,仍可以保持该连接传输后续的请求和响应报文。
  • 请求方式:HTTP 1.1 支持请求的流水线处理方式。
  • 状态响应码:HTTP/1.1中新加入了大量的状态码,光是错误响应状态码就新增了24种。比如说,100 (Continue)——在请求大资源前的预热请求,206 (Partial Content)——范围请求的标识码,409 (Conflict)——请求与当前资源的规定冲突,410 (Gone)——资源已被永久转移,而且没有任何已知的转发地址。
  • 缓存处理:在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since,If-Match,If-None-Match 等更多可供选择的缓存头来控制缓存策略。
  • 带宽优化及网络连接的使用:HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  • Host头处理:HTTP/1.1在请求头中加入了Host字段。

问:HTTP 1.1 的性能问题?⭐

  • 队头阻塞:HTTP 1.1 可以通过流水线的方式请求,即后续请求不需要等前边的请求被响应就可以发出,这解决了请求的队头阻塞。但是,服务器必须按照接收请求的顺序发送响应,如果某个请求处理时间很长,那么后续的就只能阻塞,导致响应的队头阻塞
  • 不支持服务器推送:当客户端需要获取通知时,只能通过定时器不断地拉取消息,这无疑浪费大量了带宽和服务器资源;
  • HTTP 头部巨大且重复:由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带 Cookie 的头部,而 Cookie 的大小通常很大;
  • 并发数量有限

HTTP 2.0

问:HTTP 1.1 和 HTTP 2.0 有啥区别?

HTTP/2 相比 HTTP/1.1 性能上的改进:

  • 头部压缩
  • 二进制格式
  • 并发传输
  • 服务器主动推送资源
  1. 头部压缩

HTTP/2 会压缩头(Header),如果你同时发出多个请求,他们的头是一样的或是相似的,那么就会消除重复的部分

这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

  1. 二进制帧
  • 节省空间:HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,这会节省很多字节,比如之前状态码200,之前是用 ‘2’‘0’‘0’ 3 个字符来表示(00110010 00110000 00110000),共用了 3 个字节;现在就只需 1 个字节(10001000)。
image-20230316150834722
  • 节省时间:Header 和 Body 都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)帧头一共就 9 字节。由于收到报文后,无需转换为二进制,就增加了数据传输的效率
    • 帧长度:帧数据的长度;
    • 帧类型:主要分为数据帧、控制帧。数据帧主要是传递HTTP包头和包体;
    • 帧数据:HPACK 算法压缩过的HTTP包头和包体。
image-20230416110927438
  1. 并发传输

HTTP/2 支持多个 Stream 复用在一条 TCP 连接

针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应

image-20230316152558515
  1. 服务器推送

HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。

客户端和服务器双方都可以建立 Stream。但 Stream ID 是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。

如下图,Stream 2 和 4 都是服务端主动向客户端推送的资源,属于服务端建立的 Stream。

img

HTTP 3.0(未)

问:HTTP 3.0 做了哪些优化?(未)

HTTP 3.0 将下层 TCP 协议改成了 UDP 协议。

HTTP/1 ~ HTTP/3

基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。

六、RPC

基础

问:什么是 RPC 协议?

RPC(Remote Procedure Call),又叫做远程过程调用,指的是像调用本地方法一样调用远程的方法,比较有名的就是 gRPC

举个例子,平时调用一个本地方法就像下面这样。

1
 res = localFunc(req)

如果现在这不是个本地方法,而是个远端服务器暴露出来的一个方法 remoteFunc,如果我们还能像调用本地方法那样去调用它,这样就可以屏蔽掉一些网络细节,用起来更方便,岂不美哉?

1
 res = remoteFunc(req)

RPC 和 HTTP 的区别

问:为什么有了 HTTP 协议,还要有 RPC 协议?⭐

从以下几点讨论。

服务发现

要向某个服务器发出请求,首先得建立连接,建立连接就得先知道服务器的IP地址和端口吧:

  • HTTP:通过 DNS 解析域名,得到服务器的IP地址和端口;
  • RPC:把服务器的IP地址和端口,提前注册到 ETCD 等中间件,想要访问某个服务,就去这些中间件获得 IP 和端口信息。

其实差不多,都是通过中间服务获得。

底层连接形式
  • HTTP:默认在建立底层 TCP 连接之后会一直保持这个连接(Keep Alive),之后的请求和响应都会复用这条连接
  • RPC:建个连接池,请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,非常环保。

但实际上,HTTP 应用的时候,也会加个连接池,所以说,也差不太多。

传输内容⭐

基于 TCPheader没什么不同,主要是要对body中的内容进行序列化和反序列化

  • HTTP:主要是通过 JSON
  • RPC:主要是通过 Protobuf

HTTP 示例:

image-20230415220117534

二者对比:

  • HTTP 的 Header 和 Body 都是 key-value 形式,但是key其实很多余。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 Content-Type,就不需要每次都真的把"Content-Type"这个字段都传过来,类似的情况其实在 body 的 JSON 结构里也特别明显。

  • 而 RPC,可以采用体积更小的Protobuf等定制化序列化协议(详见protobuf的md),这就是公司的微服务中使用 RPC 的==主要原因==

image-20230416090630305

七、Cookie 和 Session

HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务,HTTP/1.1 引入 Cookie 来保存状态信息

Cookie 是服务器发送到用户浏览器并==保存在本地==的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。

Cookie 的出现是因为 HTTP 是无状态的一种协议,换句话说,服务器记不住你,可能你每刷新一次网页,就要重新输入一次账号密码进行登录。这显然是让人无法接受的,Cookie 的作用就好比服务器给你贴个标签,然后你每次向服务器再发请求时,服务器就能够 Cookie 认出你。

问:Cookie 应用场景?

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

问:Session 是啥?为啥要有 Session?

Session 可以存储在服务器上的文件、数据库或者内存中,也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。

使用 Session 维护用户登录状态的过程如下:

  1. 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
  2. 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
  3. 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
  4. 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。

Cookie是客户端保持状态的方法。

**Cookie简单的理解就是存储由服务器发至客户端并由客户端保存的一段字符串。**为了保持会话,服务器可以在响应客户端请求时将Cookie字符串放在Set-Cookie下,客户机收到Cookie之后保存这段字符串,之后再请求时候带上Cookie就可以被识别。

除了上面提到的这些,Cookie在客户端的保存形式可以有两种,一种是会话Cookie,一种是持久Cookie。会话Cookie就是将服务器返回的Cookie字符串保持在内存中,关闭浏览器之后自动销毁,持久Cookie则是存储在客户端磁盘上,其有效时间在服务器响应头中被指定,在有效期内,客户端再次请求服务器时都可以直接从本地取出。需要说明的是,存储在磁盘中的Cookie是可以被多个浏览器代理所共享的。

  • Session

Session是服务器保持状态的方法。

首先需要明确的是,Session保存在服务器上,可以保存在数据库、文件或内存中,每个用户有独立的Session用户在客户端上记录用户的操作。我们可以理解为每个用户有一个独一无二的Session ID作为Session文件的Hash键,通过这个值可以锁定具体的Session结构的数据,这个Session结构中存储了用户操作行为。

==两者结合,完成服务器对客户端会话状态的保持。==每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端,然后找到相应的Session来保持会话状态。

实际上大多数的应用都是用Cookie来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在Cookie里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。如果客户端的浏览器禁用了Cookie,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如sid=xxxxx这样的参数,服务端据此来识别用户,这样就可以帮用户完成诸如用户名等信息自动填入的操作了。

一般是通过 Cookie 来保存 SessionID,假如你使用了 Cookie 保存 SessionID 的方案的话, 如果客户端禁用了 Cookie,那么 Session 就无法正常工作。

但是,并不是没有 Cookie 之后就不能用 Session,比如你可以将 SessionID 放在请求的 url 里面https://javaguide.cn/?Session_id=xxx 。这种方案的话可行,但是安全性和用户体验感降低。当然,为了你也可以对 SessionID 进行一次加密之后再传入后端。

举个例子:假如我们部署了两份相同的服务 A,B,用户第一次登陆的时候 ,Nginx 通过负载均衡机制将用户请求转发到 A 服务器,此时用户的 Session 信息保存在 A 服务器。结果,用户第二次访问的时候 Nginx 将请求路由到 B 服务器,由于 B 服务器没有保存 用户的 Session 信息,导致用户需要重新进行登陆。


有三种方法:

  1. 某个用户的所有请求都通过一致性哈希策略分配给同一个服务器处理。这样的话,每个服务器都保存了一部分用户的 Session 信息。服务器宕机,其保存的所有 Session 信息就完全丢失了。
  2. 单独使用一个所有服务器都能访问到的数据节点(比如缓存)来存放 Session 信息。为了保证高可用,数据节点尽量要避免是单点。
  3. 每一个服务器保存的 Session 信息都是互相同步的,也就是说每一个服务器都保存了全量的 Session 信息。每当一个服务器的 Session 信息发生变化,我们就将其同步到其他服务器。这种方案成本太大,并且,节点越多时,同步成本也越高。

问:Cookie-Session 和 Token 的区别?

  • Cookie-Sessioon 方案中,服务器必须保存sessionID以及该ID所代表的客户端信息。这些内容可以保存在内存,也可以保存到数据库(通常是内存数据库);而token则可以服务器完全不用保存任何登录信息,减轻了服务器的负担。

token的流程是这样的。客户端登录通过后,服务器生成一堆客户端身份信息,包括用户名、用户组、有那些权限、过期时间等等。另外再对这些信息进行签名。之后把身份信息和签名作为一个整体传给客户端。这个整体就叫做token。之后,客户端负责保存该token,而服务器不再保存。客户端每次访问该网站都要带上这个token。服务器收到请求后,把它分割成身份信息和签名,然后验证签名,若验证成功,就直接使用身份信息(用户名、用户组、有哪些权限等等)。

token相对cookie的优势:

  • 无状态 基于token的验证是无状态的,这也许是它相对cookie来说最大的优点。后端服务不需要记录token。每个令牌都是独立的,包括检查其有效性所需的所有数据,并通过声明传达用户信息。毕竟,查询session在进行对比可能会涉及到网络通信、数据库读取等,肯定是比做一次hash用时要多的。

    服务器唯一的工作就是在成功的登陆请求上签署token,并验证传入的token是否有效。

  • 防跨站请求伪造(CSRF),附带功能

  • 多站点使用 cookie绑定到单个域。foo.com域产生的cookie无法被bar.com域读取。使用token就没有这样的问题。这对于需要向多个服务获取授权的单页面应用程序尤其有用。

    使用token,使得用从myapp.com获取的授权向myservice1.com和myservice2.com获取服务成为可能。

  • 支持移动平台 好的API可以同时支持浏览器,iOS和Android等移动平台。然而,在移动平台上,cookie是不被支持的。

  • 性能 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算的Token验证和解析要费时得多。

问:Token 的存储位置?

JWT 的保存位置,可以分为如下四种:

  1. 保存在 localStorage
  2. 保存在 sessionStorage
  3. 保存在 cookie
  4. 保存在 cookie 并设置 HttpOnly

第一种和第二种其实可以归为一类,这一类有个特点,就是该域内的 js 脚本都可以读取,这种情况下 JWT 通过 js 脚本放入 Header 里的 Authorization 字段,会存在 XSS 攻击风险。

第三种,与第四种相比,区别在于 cookie 有没有标记 HttpOnly,没有标记 HttpOnly 的 cookie ,客户端可以将 JWT 通过 js 脚本放入 Header 里的 Authorization 字段。这么看好像同时存在CSRF 攻击风险和 XSS 攻击风险,实则不然,我们虽然将 JWT 存储在 cookie 里,但是我们的服务端并没有利用 cookie 里的 JWT 直接去鉴权,而是通过 header 里的 Authorization 去鉴权,因此这种方法只有 XSS 攻击风险,而没有 CSRF 攻击风险。

而第四种,加了 HttpOnly 标记,意味着这个 cookie 无法通过js脚本进行读取和修改,杜绝了 XSS 攻击的发生。与此同时,网站自身的 js 脚本也无法利用 cookie 设置 header 的Authorization 字段,因此只能通过 cookie 里的 JWT 去鉴权,所以不可避免还是存在 CSRF 攻击风险。

image-20230523154618052

开发人员应当根据实际情况来选择 JWT 的存储位置。

  • 当访问量/业务量不是很大时,可以使用 CSRF Token 来防止 CSRF 攻击
  • 而如果访问量/业务量对服务器造成很大压力,或觉得服务器共享 token 对架构要求太高了,那就抛弃CSRF Token 的方式,而改用 JWT。选择了 JWT ,就面临着要将 JWT 存储在哪的问题。
  • 若选择了 JWT ,那么请不要使用 cookie HttpCookie 来存储它,因为使用它还是会有 CSRF 攻击风险。
  • 那另外三种如何选择呢?这三种无论使用哪种,都不可避免有 XSS 攻击风险。我的思路是,XSS 攻击通过其他的手段来规避,这里使用JWT 只有 防御 CSRF 攻击与服务器性能的优化,这两个目标。
  • 那我剩下的三种,我建议是使用 cookie 存储,但不使用 cookie 来鉴权。服务器鉴权还是通过请求里的 Authorization 字段(通过js写入 Header 的)。

八、ARP

问:ARP 协议是啥?有啥用?

ARP 协议,全称 地址解析协议(Address Resolution Protocol),它解决的是网络层地址和链路层地址之间的转换问题。

问:寻址原理?

记住几个关键词:ARP 表、广播问询、单播响应

ARP 的工作原理应分两种场景讨论:

  1. 同一局域网内的 MAC 寻址
  2. 从一个局域网到另一个局域网中的网络设备的寻址

很简单,看下就会了

如果ARP协议无法获取MAC地址,通常会发生以下两种情况:

  1. 目标主机不可达:如果ARP协议无法获取目标主机的MAC地址,通常说明目标主机不可达,可能是因为目标主机已经关机、网络故障、网络拥堵等原因导致数据无法到达目标主机。在这种情况下,通常需要对网络进行故障排除,找出问题所在并进行修复。
  2. 目标主机在不同的网络中:如果ARP协议无法获取目标主机的MAC地址,但是目标主机确实存在,可能是因为目标主机和发送数据的主机在不同的网络中,需要通过路由器进行通信。在这种情况下,通常需要进行路由器配置,使得数据可以从源主机到达目标主机所在的网络,并将目标主机的MAC地址映射到正确的IP地址上。

九、网络安全

CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造

当进行 Session 认证后,每次登录时,浏览器就会默认携带 Cookie,服务端通过 Cookie 中的 SessionId 标识该用户的对话。当用户浏览攻击者的页面时,若点击到构造的请求链接,就会跳转到相应页面,浏览器会把 Cookie 发送给服务端,也就相当于攻击者以用户的身份完成了一次请求。

CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。

但是,使用 Token 就不会有这个问题。

在我们登录成功获得 Token 之后,一般会选择存放在 localStorage (浏览器本地存储)中。然后我们在前端(js)通过某些方式会给每个发到后端请求的Authorization中加上这个 Token,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 Token 的,所以这个请求将是非法的。

问:什么是 SYN 攻击?如何避免 SYN 攻击?

攻击者伪造大量的 SYN 报文,占满服务端的半连接队列,造成拒绝服务。因为当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。

如何防御?

  • 设置更大的半连接队列
  • 减少 SYN+ACK 的重传次数,以加快处于 SYN_RECV 状态的 TCP 连接断开;这是因为处于 SYN_RECV 状态的 TCP 连接会重传 SYN+ACK ,当重传超过次数达到上限后,就会断开连接;
  • **开启 syncookies 功能。**可以在不使用 SYN 半连接队列的情况下成功建立连接,相当于绕过了 SYN 半连接来建立连接;
  • 部署入侵检测系统IDS,识别并过滤虚假IP地址