计算机网络面试八股文总结:归纳核心知识点(3)
[复制链接]
19.TCP 握手为什么是三次,为什么不能是两次?不能是四次?
为什么不能是两次?
防止已失效的连接请求报文段突然又传到服务端,因而产生错误
客户端发送出去的第一个连接请求报文段并没有丢失,而是因为某些未知原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间点才到达服务端。
本来这是一个早已失效的报文段,但是服务端收到此失效的报文段后,会误认为这是客户端再次发起的一个新的连接请求,于是服务端向客户端又发出确认报文,表示同意建立连接。
如果不采用 “三次握手”,那么只要服务端发出确认报文后就会认为新的连接已经建立了,但是客户端并没有发出建立连接的请求,因此不会向服务端发送数据,服务端没有收到数据就会一直等待,这样服务端就会白白浪费掉很多资源。
所以我们需要 “第三次握手” 来确认这个过程:
通过第三次握手的数据来告诉服务端,客户端有没有收到服务端 “第二次握手” 时传过去的数据,以及这个连接的序号是不是有效的。
若发送的这个数据是 “收到且没有问题” 的信息,服务端接收后就可以正常建立 TCP 连接,否则建立 TCP 连接失败,服务器关闭连接端口。由此减少服务器开销和接收到失效请求时发生的错误。
为什么不是四次?
简单来说,就是三次握手已经足够创建可靠的连接,没有必要再多一次握手导致花费更多的时间在建立连接上。
20.三次握手中每一次没收到报文会发生什么情况?
第一次握手服务端未收到 SYN 报文
服务端不会进行任何的动作,而客户端由于一段时间内没有收到服务端发来的确认报文,等待一段时间后会重新发送 SYN 报文,
如果仍然没有回应,会重复这个过程,直到发送次数超过最大重传次数,就会返回连接建立失败。
第二次握手客户端未收到服务端响应的 ACK 报文
因为第二次握手是包含对客户端第一次握手的 ACK 确认报文,所以如果客户端迟迟没有收到第二次握手,那么客户端就会觉得可能是自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。
然后,因为第二次握手是包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。
那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。
第三次握手服务端未收到客户端发送过来的 ACK 报文
客户端收到服务端的 SYN-ACK 报文后,就会给服务端发送一个 ACK 报文,也就是第三次握手,此时客户端进入到 ESTABLISH(连接已建立) 状态。
因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
21.第二次握手传回了 ACK,为什么还要传回 SYN?
ACK 是为了告诉客户端传来的数据已经接收无误。
而传回 SYN 是为了告诉客户端,服务端响应的确实是客户端发送的报文。
22.第三次握手可以携带数据吗?
第三次握手是可以携带数据的。
此时客户端已经处于连接已建立状态。对于客户端来说,它已经建立连接成功了,并且确认服务端的接收和发送能力是正常的。
第一次握手不能携带数据是出于安全的考虑,因为如果允许携带数据,攻击者每次在 SYN 报文中携带大量数据,就会导致服务端消耗更多的时间和空间去处理这些报文,会造成CPU和内存的消耗。
23.说说 TCP 四次挥手的过程?
-
数据传输结束后,通信双方都可以主动发起断开连接请求,这里假定客户端发起。
-
客户端发送释放连接报文,进行第一次挥手(FIN=1,ACK=1,seq=u,ack=v),发送完毕后,客户端进入 FIN_WAIT_1(终止等待1) 状态。
-
服务端发送确认报文,进行第二次挥手(ACK=1,seq =v,ack=u+1),发送完毕后,服务端进入 CLOSE_WAIT(关闭等待) 状态,客户端收到这个确认包后,进入 FIN_WAIT_2(终止等待2) 状态。
-
服务端发送释放连接报文,进行第三次挥手(FIN=1,ACK1,seq=w,ack=u+1),发送完毕后,服务端进入LAST_ACK(最后确认) 状态,等待来自客户端的最后一个 ACK 报文。
-
客户端发送确认报文,进行第四次挥手(ACK=1,seq=u+1,ack=w+1),客户端收到来自服务端的关闭请求,发送一个确认包,并进入 TIME_WAIT(时间等待) 状态,服务端接收到这个确认包后,关闭连接,进入 CLOSED(关闭) 状态。
-
客户端再经过 2MSL 后,也进入 CLOSED(关闭) 状态。
客户端在发送完最后一个确认报文后,为什么不直接进入关闭状态 ? 而是要进入时间等待状态,2MSL 后才进入关闭状态,这是否有必要呢 ?
服务端发送TCP连接释放报文段后进入最后确认状态。
客户端收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。然而,该TCP确认报文段丢失了。
这必然会造成服务端对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态。重传的TCP连接释放报文段到达客户端,由于客户端处于关闭状态,因此不理睬该报文段,这必然会造成服务端反复重传TCP连接释放报文段,并一直处于最后确认状态而无法进入关闭状态。
因此时间等待状态以及处于该状态2MSL时长,可以确保服务端可以收到最后一个TCP确认报文段而进入关闭状态。
另外,客户端在发送完最后一个TCP确认报文段后,再经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段。
为什么等待时间是2MSL?
MSL 是报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间报⽂将被丢弃。
TIME_WAIT 等待 2 倍的 MSL,是因为⽹络中可能存在来⾃发送⽅的数据包,当这些发送⽅的数据包被接收⽅处理后⼜会向对⽅发送响应,所以⼀来⼀回需要等待 2 倍的时间。
⽐如服务端如果没有收到客户端发送的TCP确认报文段,就会触发超时重传,重新发送TCP连接释放报文段,客户端收到后,会重发TCP确认报文段给服务端, ⼀来⼀去正好 2 个 MSL。
24.TCP 挥手为什么需要四次呢?
再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。
关闭连接时,客户端向服务端发送 FIN 报文,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN 报文后,先返回一个 ACK 确认报文;而服务端可能还有数据需要处理和发送,等服务端不再发送数据了,再发送 FIN 报文给客户端来表示同意现在关闭连接。
从上面的过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而导致比三次握手多了一次。
25.TCP 保活计时器有什么用?
除了时间等待计时器外,TCP 还有一个保活计时器(keepalive timer)。
设想这样的场景:
TCP 双方已经建立了连接,后来,客户端的主机突然出现了故障。显然,服务端以后就不能再收到客户端发来的数据。因此,应当有措施使服务端不要再白白等待下去。这就需要使用保活计时器了。
服务端每收到一次客户端的数据,就重新设置并启动保活计时器(2小时定时)。若定时周期内都没有收到客户端发来的数据,服务端就发送一个探测报文段,以后每隔 75 秒钟发送一次。若连续发送 10 个探测报文段后仍然无客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。
26.CLOSE-WAIT 的状态和意义?
服务端收到客户端关闭连接的请求并确认之后,就会进入 CLOSE-WAIT 状态。
此时服务端可能还有一些数据没有传输完成,因此不能立即关闭连接,而 CLOSE-WAIT 状态就是为了保证服务端在关闭连接之前将待发送的数据处理完。
27.说说 TCP 报文首部有哪些字段?
16位端口号:源端口号,标识发送该 TCP 报文段的应用进程;目的端口号,标识接收该 TCP 报文段的应用进程。
32位序号:指出本 TCP 报文段数据载荷的第一个字节的序号。32位序号:指出本 TCP 报文段数据载荷的第一个字节的序号。
32位确认号:指出期望收到对方下一个 TCP 报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号 = n,则表明到序号 n-1 为止的所有数据都已正确接收,期望收到序号为 n 的数据。
4位头部长度:指出 TCP 报文段的首部长度。
6位标志位:
16位窗口大小:
16位校验和:用来检查整个 TCP 报文段在传输过程中是否出现了误码。
16位紧急指针:当发送方有紧急数据时,可将紧急数据插队到发送缓存的最前面,并立刻封装到一个 TCP 报文段中进行发送。紧急指针会指出本报文段的数据载荷部分包含了多长的紧急数据,紧急数据之后是普通数据。
28.TCP 是如何保证可靠性的?
TCP主要提供了 连接管理、校验和、序列号/确认应答、流量控制、最大消息长度、超时重传、拥塞控制等方式实现了可靠传输。
连接管理:TCP 使用三次握手和四次挥手来保证可靠地建立连接和释放连接。
校验和:用来检查整个 TCP 报文段在传输过程中是否出现了误码。
序列号/确认应答:TCP 会给发送的每一个包进行编号,接收方会对收到的包进行应答,发送方就会知道接收方是否收到对应的包,如果发现没有收到,就会重发,这样就能保证数据的完整性了。
流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据大小。当接收方来不及处理发送方的数据时,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。(TCP 利用滑动窗口实现流量控制)
最大消息长度:在建立 TCP 连接的时候,双方约定一个最大的长度(MSS)作为发送的单位,重传的时候也是以这个单位来进行重传的。理想情况下是该长度的数据刚好不被网络层分块。
超时重传:超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间,就会被认为是丢包了,需要重传。
拥塞控制:如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可能超过了最大生存时间也没有到达接收方,就会产生丢包问题。为此 TCP 引入了慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。
29.说说 TCP 的流量控制?
TCP 提供了一种机制,可以让发送方根据接收方的实际接收能力控制发送的数据量,这就是流量控制。
TCP 通过「滑动窗口」来实现流量控制
首先 TCP 双方进行三次握手,初始化各自的窗口大小,均为 400 字节。
假如当前发送方给接收方发送了 200 字节,那么,发送方的SND.NXT会右移 200 字节,也就是说当前的可用窗口减少了 200 字节。
接收方收到后,放到缓冲队列里面,REV.WND = 400-200=200 字节,所以 win=200 字节返回给发送方。接收方会在 ACK 的报文首部带上缩小后的滑动窗口 200 字节
发送方又发送 200 字节过来,200 字节到达,继续放到缓冲队列里面。不过这时候,由于大量负载的原因,接收方处理不了这么多字节,只能处理 100 字节,剩余的 100 字节继续放到缓冲队列里面。这时候,REV.WND = 400-200-100=100 字节,即 win=100 字节返回给发送方。
发送方继续发送 100 字节过来,这时候,接收窗口 win 变为 0。
发送方停止发送,开启一个定时任务,每隔一段时间,就去询问接收方,直到 win 大于 0,才开始继续发送。
30.详细说说 TCP 的滑动窗口?
TCP 发送一个数据,如果需要收到确认应答才会发送下一个数据。这样的话就会有个缺点:效率会比较低。
为了解决这个问题,TCP 引入了滑动窗口,它是操作系统开辟的一个缓存空间。窗口大小表示无需等待确认应答而可以继续发送数据的最大值。
TCP 头部有个 16 位的窗口大小,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。
通俗点讲,就是接收方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的接收缓冲区还有多少空闲空间,缓冲区的空闲空间,我们就称之为接收窗口大小。
TCP 滑动窗口分为两种: 发送窗口和接收窗口。
发送方的滑动窗口包含四个部分:
-
已发送且已收到 ACK 确认
-
已发送但未收到 ACK 确认
-
未发送但可以发送
-
未发送且不可发送
-
虚线矩形框,就是发送窗口。
-
SND.WND:表示发送窗口的大小,上图虚线框的格子数是 14 个,即发送窗口大小是 14。
-
SND.NXT:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。
-
SND.UNA:一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号。
接收方的滑动窗口包含三个部分:
-
已成功接收并确认
-
未收到数据但可以接收
-
未收到数据且不可以接收的数据
31.说说 TCP 的拥塞控制?
什么是拥塞控制?不是有了流量控制吗?
前⾯的流量控制是避免发送⽅的数据填满接收⽅的缓存,但是并不知道整个⽹络中发⽣了什么。
⼀般来说,计算机⽹络都处在⼀个共享的环境。因此也有可能会因为其他主机之间的通信使得⽹络出现拥堵。
在⽹络出现拥堵时,如果继续发送⼤量数据包,可能会导致数据包延迟、丢失等,这时 TCP 就会重传数据,但是⼀重传就会导致⽹络的负担更重,于是会导致更⼤的延迟以及更多的丢包,这个情况就会进⼊恶性循环并且被不断地放⼤…
所以,TCP 不能忽略整个网络中发⽣的事,它被设计成⼀个⽆私的协议,当⽹络发送拥塞时,TCP 会⾃我牺牲,降低发送的数据流。
于是,就有了拥塞控制,拥塞控制的⽬的就是为了避免发送⽅的数据填满整个⽹络。
就像是一个水管,不能让太多的水(数据流)流入水管,如果超过水管的承受能力,水管就会被撑爆(丢包)。
发送方维护一个拥塞窗口 cwnd(congestion window) 的变量,调节所要发送数据的量。
什么是拥塞窗⼝?和发送窗⼝有什么关系呢?
拥塞窗⼝ **cwnd **是发送⽅维护的⼀个状态变量,它会根据⽹络的拥塞程度动态变化。
发送窗⼝ swnd 和接收窗⼝ rwnd 是约等于的关系,那么由于加⼊了拥塞窗⼝的概念后,此时发送窗⼝的值 swnd = min(cwnd, rwnd),也就是取拥塞窗⼝和接收窗⼝中的最⼩值。
拥塞窗⼝ cwnd 变化的规则:
-
只要⽹络中没有出现拥塞, cwnd 就会增⼤
-
但如果⽹络中出现了拥塞, cwnd 就会减小
拥塞控制有哪些常用算法?
慢启动
慢启动算法,慢慢启动。
它表示 TCP 建立连接完成后,一开始不要发送大量的数据,而是先探测一下网络的拥塞程度。由小到大逐渐增加拥塞窗口的大小,如果没有出现丢包,每收到一个 ACK,就将拥塞窗口 cwnd 的大小加 1(单位是 MSS)。每轮次发送窗口增加一倍,呈指数增长,如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
举个例子:
-
连接建⽴完成后,⼀开始初始化 cwnd = 1 ,表示可以传⼀个 MSS ⼤⼩的数据。
-
当收到⼀个 ACK 确认应答后,cwnd 增加 1,于是⼀次性能够发送 2 个。
-
当收到 2 个 ACK 确认应答后, cwnd 增加 2,于是就能⽐之前多发送 2 个,所以这⼀次能够发送 4 个。
-
当这 4 个 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就能⽐之前多发送 4 个,所以这⼀次能够发送 8 个。
发送包的个数是呈指数性增⻓的。
为了防止 cwnd 增长过大而引起网络拥塞,还需设置一个慢启动阀值 ssthresh(slow start threshold)的状态变量。当 cwnd 到达该阀值后,就好像水管被关小了水龙头一样,减少了拥塞状态。即当 cwnd > ssthresh 时,进入拥塞避免算法。
拥塞避免
一般来说,慢启动阀值 ssthresh 的大小是 65535 字节,cwnd 到达慢启动阀值后
显然这是一个线性上升的算法,可以避免发送过快导致网络出现拥塞问题。
接着上面慢启动的例子,假定 ssthresh 为 8:
拥塞发生
当网络拥塞发生丢包时,会有两种情况:
如果是发生了RTO 超时重传,就会使用「拥塞发生」算法
-
慢启动阀值 sshthresh = cwnd/2
-
cwnd 重置为 1
-
进入新的慢启动过程
这真的是辛辛苦苦几十年,一朝回到解放前。其实还有更好的处理方式,就是「快速重传」。当发送方收到 3 个连续的重复 ACK 时,就会快速地重传,不必等待RTO超时再重传。
发⽣「快速重传」的拥塞发⽣算法:
-
拥塞窗口大小 cwnd = cwnd/2
-
慢启动阀值 ssthresh = cwnd
-
进入快速恢复算法
快速恢复
快速重传和快速恢复算法一般是同时使用的。快速恢复算法认为,还能收到 3 个重复的 ACK,说明网络也没有那么糟糕,所以没必要像 RTO超时重传 那样强烈。
正如前面所说的,进入快速恢复之前,cwnd 和 sshthresh 已被更新:
-
cwnd = cwnd/2
-
sshthresh = cwnd
然后,真正进入「快速恢复」算法:
32.说说 TCP 的重传机制?
重传包括:超时重传、快速重传、带选择确认的重传(SACK)、重复 SACK 四种。
超时重传
超时重传,是 TCP 协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个重传计时器,在一定时间内如果没有收到发送的数据报的 ACK 报文,那么就重新发送数据,直到收到 ACK 报文为止。
超时时间应该设置为多少合适呢?
RTT 就是数据完全发送完,到收到确认信号的时间,即数据包的一次往返时间。
超时重传时间,就是 RTO(Retransmission Timeout)。那么,RTO 应该设置多大呢?
一般来说,RTO 略微大于 RTT,效果是最佳的。
超时重传并不是十分完美的重传方案,它有这些缺点:
快速重传
快速重传可以用来解决超时重发的时间等待问题。
它不以时间驱动,而是以数据驱动。它是基于接收方的反馈信息来引发重传的。
快速重传的流程如下:
发送方发送了 1,2,3,4,5,6 份数据:
-
第一份 Seq=1 先送到了,于是 ACK 回2;
-
第二份 Seq=2 也送到了,于是 ACK 回3;
-
第三份 Seq=3 由于网络等某些原因,没送到;
-
第四份 Seq=4 送到了,但是由于 Seq=3 没收到。因此 ACK 还是回3;
-
后面的 Seq=5,6 也送到了,ACK 还是回复3,因为 Seq=3 没有收到。
-
发送方连续收到三个重复冗余的 ACK=3 的确认(其实是4个哈,但是因为前面的一个是正常的ACK,后面三个才是重复冗余的),于是知道哪个报文段在传输过程中丢失了;发送方就在重传定时器过期之前,重传该报文段。
-
最后,接收方收到了 Seq=3,此时因为 Seq=4,5,6 都收到了,于是它回 ACK=7。
快速重传机制也有缺点:发送方并不知道到底是哪个报文丢失了,到底该重传多少个数据包?
是只重传 Seq=3 ?还是重传 Seq=3、Seq=4、Seq=5、Seq=6 呢?因为发送方并不清楚这三个连续的 ACK=3 是谁传回来的。
带选择确认的重传(SACK)
为了解决应该重传多少个包的问题? TCP 提供了带选择确认的重传(即 SACK,Selective Acknowledgment)。
SACK 机制就是,在快速重传的基础上,接收方返回最近收到报文段的序列号范围,这样发送方就知道接收方哪些数据包是没收到的。这样就很清楚应该重传哪些数据包。
如上图中,发送⽅收到了三次同样的 ACK 确认报⽂,于是就会触发「快速重传」机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进⾏重发。
重复 SACK(D-SACK)
D-SACK,英文是 Duplicate SACK,是在 SACK 的基础上做了一些扩展,主要用来告诉发送方,有哪些数据包,自己重复接受了。
D-SACK 的目的是帮助发送方判断,是否发生了包失序、ACK 丢失、包重复或伪重传。让 TCP 可以更好的做网络流控。
例如 ACK 丢包导致的数据包重复:
-
接收⽅发给发送⽅的两个 ACK 确认应答都丢失了,所以发送⽅超时后,重传第⼀个数据包(3000 ~ 3499)
-
接收⽅发现数据是重复收到的,于是回了⼀个 SACK = 3000~3500,告诉「发送⽅」 3000~3500的数据早已被接收了,因为 ACK 都到 4000 了,意味着 4000 之前的所有数据都已经收到了,所以这个 SACK 就代表着 D-SACK 。这样发送⽅就知道了,数据并没有丢,而是接收⽅的 ACK 确认报⽂丢了。
33.说说 TCP 的粘包和拆包?
什么是 TCP 粘包和拆包?
TCP 是面向字节流,没有界限的一串数据。TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包进行发送,这就是所谓的 TCP 粘包和拆包问题。
为什么会产生粘包和拆包呢?
-
要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次性发送出去,将会发生粘包;
-
接收方的应用层没有及时读取接收缓冲区的数据,将会发生粘包;
-
要发送的数据大于 TCP 发送缓冲区剩余空间的大小,将会发生拆包;
-
待发送的数据大于 MSS(最大报文长度),TCP 在传输前将会进行拆包。即 TCP报文长度 - TCP头部长度 > MSS。
解决方案:
-
发送方给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收方在收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
-
发送方将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收方每次从接收缓冲区中读取固定长度的数据,就自然而然的把每个数据包拆分开来。
-
可以在数据包之间设置边界,如添加特殊符号,这样接收方通过这个边界就可以将不同的数据包拆分开来。
|