主题
《面渣逆袭》计算机网络 篇 · 第 4/7 章。原版 PDF(下载 / 打印)
24.详细说一下 TCP 的三次握手机制
PS:TCP 三次握手是最重要的知识点,一定要熟悉到问到即送分。
TCP 提供面向连接的服务,在传送数据前必须建立连接,TCP 连接是通过三次握手建立的。

三次握手的过程:
最开始,客户端和服务端都处于 CLOSE 状态,服务端监听客户端的请求,进入 LISTEN 状态
客户端端发送连接请求,第一次握手 (SYN=1, seq=x),发送完毕后,客户端就进入 SYN_SENT状态
服务端确认连接,第二次握手 (SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端就进入 SYN_RCV 状态。
客户端收到服务端的确认之后,再次向服务端确认,这就是* * 第三次握手 (ACK=1 ,
ACKnum=y+1) ,发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态。
TCP 三次握手通俗比喻:
在二十年前的农村,电话没有普及,手机就更不用说了,所以,通信基本靠吼。
老张和老王是邻居,这天老张下地了,结果家里有事,热心的邻居老王赶紧跑到村又,开始叫唤老王。
老王:老张唉!我是老王,你能听到吗?
老张一听,是老王的声音:老王老王,我是老张,我能听到,你能听到吗?
老王一听,嗯,没错,是老张:老张,我听到了,我有事要跟你说。
" 你老婆要生了,赶紧回家吧!"
老张风风火火地赶回家,老婆顺利地生了个带把的大胖小子。握手的故事充满了幸福和美满。

25.TCP 握手为什么是三次,为什么不能是两次?不能是四次?
为什么不能是两次?
为了防止服务器端开启一些无用的连接增加服务器开销防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
由于网络传输是有延时的( 要通过网络光纤和各种中间代理服务器) ,在传输的过程中,比如客户端发起了 SYN=1 的第一次握手。
如果服务器端就直接创建了这个连接并返回包含 SYN 、ACK 和 Seq 等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直没有接收到服务器返回的数据包。
如果没有第三次握手告诉服务器端客户端收的到服务器端传输的数据的话,服务器端是不知道客户端有没有接收到服务器端返回的信息的。
服务端就认为这个连接是可用的,端又就一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端又连接。这样一来,就会有很多无效的连接端又白白地开着,导致资源的浪费。

还有一种情况是已经失效的客户端发出的请求信息,由于某种原因传输到了服务器端,服务器端以为是客户端发出的有效请求,接收后产生错误。

所以我们需要“ 第三次握手” 来确认这个过程:
通过第三次握手的数据告诉服务端,客户端有没有收到服务器“ 第二次握手” 时传过去的数据,以及这个连接的序号是不是有效的。若发送的这个数据是“收到且没有问题” 的信息,接收后服务器就正常建立 TCP 连接,否则建立 TCP 连接失败,服务器关闭连接端又。由此减少服务器开销和接收到失效请求发生的错误。
为什么不是四次?
简单说,就是三次挥手已经足够创建可靠的连接,没有必要再多一次握手导致花费更多的时间建立连接。
26.三次握手中每一次没收到报文会发生什么情况?
第一次握手服务端未收到 SYN 报文服务端不会进行任何的动作,而客户端由于一段时间内没有收到服务端发来的确认报文,等待一段时间后会重新发送 SYN 报文,如果仍然没有回应,会重复这个过程,直到发送次数超过最大重传次数限制,就会返回连接建立失败。
第二次握手客户端未收到服务端响应的 ACK 报文客户端会继续重传,直到次数限制;而服务端此时会阻塞在 accept() 处,等待客户端发送 ACK 报文第三次握手服务端为收到客户端发送过来的 ACK 报文服务端同样会采用类似客户端的超时重传机制,如果重试次数超过限制,则 accept() 调用返回- 1 ,服务端建立连接失败;而此时客户端认为自己已经建立连接成功,因此开始向服务端发送数据,但是服务端的 accept() 系统调用已经返回,此时不在监听状态,因此服务端接收到客户端发送来的数据时会发送 RST 报文给客户端,消除客户端单方面建立连接的状态。
27.第二次握手传回了 ACK,为什么还要传回 SYN?
ACK 是为了告诉客户端传来的数据已经接收无误。
而传回 SYN 是为了告诉客户端,服务端响应的确实是客户端发送的报文。
28.第 3 次握手可以携带数据吗?
第 3 次握手是可以携带数据的。
此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,它已经建立连接成功,并且确认服务端的接收和发送能力是正常的。
第一次握手不能携带数据是出于安全的考虑,因为如果允许携带数据,攻击者每次在 SYN 报文中携带大量数据,就会导致服务端消耗更多的时间和空间去处理这些报文,会造成 CPU 和内存的消耗。
29.说说半连接队列和 SYN Flood 攻击的关系?
什么是半连接队列?
TCP 进入三次握手前,服务端会从CLOSED状态变为LISTEN状态, 同时在内部创建了两个队列:
半连接队列(SYN 队列)和全连接队列(ACCEPT 队列)。

顾名思义,半连接队列存放的是三次握手未完成的连接,全连接队列存放的是完成三次握手的连接。
TCP 三次握手时,客户端发送 SYN 到服务端,服务端收到之后,便回复ACK 和 SYN,状态由
LISTEN 变为 SYN_RCVD,此时这个连接就被推入了SYN 队列,即半连接队列。
当客户端回复 ACK, 服务端接收后,三次握手就完成了。这时连接会等待被具体的应用取走,在被取走之前,它被推入 ACCEPT 队列,即全连接队列。
什么是 SYNFlood ?
SYNFlood 是一种典型的 DDos 攻击,它在短时间内,伪造不存在的 IP 地址, 向服务器发送大量
SYN 报文。当服务器回复 SYN+ACK 报文后,不会收到 ACK 回应报文,那么 SYN 队列里的连接旧不会出对队,久而久之就会占满服务端的SYN接收队列(半连接队列),使得服务器不能为正常用户服务。

那有什么应对方案呢?
主要有syncookie和SYNProxy 防火墙等。
syncookie:在收到 SYN 包后,服务器根据一定的方法,以数据包的源地址、端又等信息为参数计算出一个 cookie 值作为自己的 SYNACK 包的序列号,回复 SYN+ACK 后,服务器并不立即分配资源进行处理,等收到发送方的 ACK 包后,重新根据数据包的源地址、端又计算该包中的确认序列号是否正确,如果正确则建立连接,否则丢弃该包。
SYNProxy 防火墙:服务器防火墙会对收到的每一个 SYN 报文进行代理和回应,并保持半连接。等发送方将 ACK 包返回后,再重新构造 SYN 包发到服务器,建立真正的 TCP 连接。
30.说说 TCP 四次挥手的过程?
PS :问完三次握手,常常也会顺道问问四次挥手,所以也是必须掌握知识点。

TCP 四次挥手过程:
数据传输结束之后,通信双方都可以主动发起断开连接请求,这里假定客户端发起客户端发送释放连接报文,第一次挥手(FIN=1 ,s eq=u) ,发送完毕后,客户端进入
FIN_WAIT_1状态。
服务端发送确认报文,第二次挥手 (ACK=1,ack=u+1,seq =v),发送完毕后,服务器端进入CLOSE_WAIT状态,客户端接收到这个确认包之后,进入FIN_WAIT_2状态。
服务端发送释放连接报文,第三次挥手 (FIN=1,ACK1,seq=w,ack=u+1),发送完毕后,服务器端进入LAST_ACK状态,等待来自客户端的最后一个 ACK 。
客户端发送确认报文,第四次挥手 (ACK=1,seq=u+1,ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待了某个固定时间(两个最大段生
命周期,2 MSL ,2 MaximumSegmentLifetime )之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
大白话说四次挥手:
假如单身狗博主有一个女朋友— 由于博主上班九九六,下班肝博客,导致没有时间陪女朋友,女朋友忍无可忍。
女朋友:狗男人,最近你都不理我,你是不是不爱我了?你是不是外面有别的狗子了?我要和你分手?
沙雕博主一愣,怒火攻心:分手就分手,不陪你闹了,等我把东西收拾收拾。
沙雕博主小心翼翼地装起了自己的青轴机械键盘。
哼,蠢女人,我已经收拾完了,我先滚为敬,再见!
女朋友:滚,滚的远远的,越远越好,我一辈子都不想再见到你。
挥手的故事总充满了悲伤和遗憾!

31.TCP 挥手为什么需要四次呢?
再来回顾下四次挥手双方发FIN包的过程,就能理解为什么需要四次了。
关闭连接时,客户端向服务端发送FIN时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的ACK和FIN一般都会分开发送,从而比三次握手导致多了一次。
32.TCP 四次挥手过程中,为什么需要等待 2MSL, 才进入 CLOSED 关闭 状态?
为什么需要等待?
1. 为了保证客户端发送的最后一个 ACK 报文段能够到达服务端。这个 ACK 报文段有可能丢失,因而使处在LAST-ACK状态的服务端就收不到对已发送的FIN+ACK报文段的确认。服务端会超时重传这个 FIN+ACK 报文段,而客户端就能在 2MSL 时间内(超时 +1MSL 传输)收到这个重传的
FIN+ACK 报文段。接着客户端重传一次确认,重新启动 2MSL 计时器。最后,客户端和服务器都正常进入到CLOSED状态。
2. 防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL ,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。
为什么等待的时间是 2MSL ?
MSL 是 MaximumSegmentLifetime ,报文最大生存时间,它是任何报文在网络上存在的最⻓时间,超过这个时间报文将被丢弃。
TIME_WAIT 等待 2 倍的 MSL ,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待2倍的时间。

比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL 。
33.保活计时器有什么用?
除时间等待计时器外,TCP 还有一个保活计时器(keepalivetimer )。
设想这样的场景:客户已主动与服务器建立了 TCP 连接。但后来客户端的主机突然发生故障。显然,服务器以后就不能再收到客户端发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就需要使用保活计时器了。
服务器每收到一次客户端的数据,就重新设置保活计时器,时间的设置通常是两个小时。若两个小时都没有收到客户端的数据,服务端就发送一个探测报文段,以后则每隔 75 秒钟发送一次。若连续发送10 个探测报文段后仍然无客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。
34.CLOSE-WAIT 和 TIME-WAIT 的状态和意义?
CLOSE-WAIT 状态有什么意义?
服务端收到客户端关闭连接的请求并确认之后,就会进入 CLOSE-WAIT 状态。此时服务端可能还有一些数据没有传输完成,因此不能立即关闭连接,而 CLOSE-WAIT 状态就是为了保证服务端在关闭连接之前将待发送的数据处理完。
TIME-WAIT 有什么意义?
TIME-WAIT 状态发生在第四次挥手,当客户端向服务端发送 ACK 确认报文后进入 TIME-WAIT 状态。
它存在的意义主要是两个:

防止旧连接的数据包
如果客户端收到服务端的 FIN 报文之后立即关闭连接,但是此时服务端对应的端又并没有关闭,如果客户端在相同端又建立新的连接,可能会导致新连接收到旧连接残留的数据包,导致不可预料的异常发生。
保证连接正确关闭
假设客户端最后一次发送的 ACK 包在传输的时候丢失了,由于 TCP 协议的超时重传机制,服务端将重发 FIN 报文,如果客户端没有维持 TIME-WAIT 状态而直接关闭的话,当收到服务端重新发送的
FIN 包时,客户端就会使用 RST 包来响应服务端,导致服务端以为有错误发生,然而实际关闭连接过程是正常的。
35.TIME_WAIT 状态过多会导致什么问题?怎么解决?
TIME_WAIT 状态过多会导致什么问题?
如果服务器有处于 TIME-WAIT 状态的 TCP ,则说明是由服务器方主动发起的断开请求。
过多的 TIME-WAIT 状态主要的危害有两种:
第一是内存资源占用;
第二是对端又资源的占用,一个 TCP 连接至少消耗一个本地端又;
怎么解决 TIME_WAIT 状态过多?
服务器可以设置 SO_REUSEADDR 套接字来通知内核,如果端又被占用,但是 TCP 连接位于
TIME_WAIT 状态时可以重用端又。
还可以使用长连接的方式来减少 TCP 的连接和断开,在长连接的业务里往往不需要考虑
TIME_WAIT 状态。
36.说说 TCP 报文首部的格式?
看一下 TCP 报文首部的格式:

16 位端又号:源端又号,主机该报文段是来自哪里;目标端又号,要传给哪个上层协议或应用程序
32 位序号:一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
32 位确认号:用作对另一方发送的 tcp 报文段的响应。其值是收到的 TCP 报文段的序号值加1 。
4 位首部长度:表示 tcp 头部有多少个 32bit 字(4 字节)。因为 4 位最大能标识 15 ,所以
TCP 头部最长是 60 字节。
6 位标志位:URG( 紧急指针是否有效) ,ACk (表示确认号是否有效),PST (缓冲区尚未填满),RST (表示要求对方重新建立连接),SYN (建立连接消息标志接),FIN (表示告知对方本端要关闭连接了)
16 位窗又大小:是 TCP 流量控制的一个手段。这里说的窗又,指的是接收通告窗又。它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
16 位校验和:由发送端填充,接收端对 TCP 报文段执行 CRC 算法以检验 TCP 报文段在传输过程中是否损坏。注意,这个校验不仅包括 TCP 头部,也包括数据部分。这也是 TCP 可靠传输的一个重要保障。
16 位紧急指针:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP 的紧急指针是发送端向接收端发送紧急数据的方法。
37.TCP 是如何保证可靠性的?
TCP 主要提供了检验和、序列号/ 确认应答、超时重传、最大消息长度、滑动窗又控制等方法实现了可靠性传输。

连接管理:TCP 使用三次握手和四次挥手保证可靠地建立连接和释放连接,这里就不用多说了。
校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输
过程中的任何变化。如果接收端的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。

TCP 校验和
- 序列号/ 确认应答:TCP 给发送的每一个包进行编号,接收方会对收到的包进行应答,发送方就
会知道接收方是否收到对应的包,如果发现没有收到,就会重发,这样就能保证数据的完整性。
就像老师上课,会问一句,这一章听懂了吗?没听懂再讲一遍。

- 流量控制:* *TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送
接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗又协议。(TCP 利用滑动窗又实现流量控制)

- 最大消息长度:在建立 TCP 连接的时候,双方约定一个最大的长度(MSS )作为发送的单位,
重传的时候也是以这个单位来进行重传。理想的情况下是该长度的数据刚好不被网络层分块。

- 超时重传:* * 超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时
间会被认为是丢包了,需要重传。

- 拥塞控制:* * 如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可
能超过了最大生存时间也没有到达接收方,就会产生丢包问题。为此 TCP 引入慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。

38.说说 TCP 的流量控制?
TCP 提供了一种机制,可以让发送端根据接收端的实际接收能力控制发送的数据量,这就是流量控
制。
TCP 通过滑动窗又来控制流量,我们看下简要流程:
首先双方三次握手,初始化各自的窗又大小,均为 400 个字节。

假如当前发送方给接收方发送了 200 个字节,那么,发送方的SND.NXT会右移 200 个字节,也就是说当前的可用窗又减少了 200 个字节。
接受方收到后,放到缓冲队列里面,REV.WND=400-200=200 字节,所以 win=200 字节返回给发送方。接收方会在 ACK 的报文首部带上缩小后的滑动窗又 200 字节发送方又发送 200 字节过来,2 00 字节到达,继续放到缓冲队列。不过这时候,由于大量负载的原因,接受方处理不了这么多字节,只能处理 100 字节,剩余的 100 字节继续放到缓冲队列。这时候,REV.WND=400-200-100=100 字节,即 win=100 返回发送方。
发送方继续发送 100 字节过来,这时候,接收窗又 win 变为 0 。
发送方停止发送,开启一个定时任务,每隔一段时间,就去询问接受方,直到 win 大于 0 ,才继续开始发送。
39.详细说说 TCP 的滑动窗又?
TCP 发送一个数据,如果需要收到确认应答,才会发送下一个数据。这样的话就会有个缺点:效率会比较低。
“ 用一个比喻,我们在微信上聊天,你打完一句话,我回复一句之后,你才能打下一句。假如我没有及时回复呢?你是把话憋着不说吗?然后傻傻等到我回复之后再接着发下一句?”为了解决这个问题,TCP 引入了窗又,它是操作系统开辟的一个缓存空间。窗又大小值表示无需等待确认应答,而可以继续发送数据的最大值。
TCP 头部有个字段叫 win ,也即那个16 位的窗又大小,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。
“ 通俗点讲,就是接受方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的缓存区还有多少空余空间,缓冲区的空余空间,我们就称之为接受窗又大小。这就是 win 。”
TCP 滑动窗又分为两种: 发送窗又和接收窗又。发送端的滑动窗又包含四大部分,如下:
已发送且已收到 ACK 确认已发送但未收到 ACK 确认未发送但可以发送未发送也不可以发送

深蓝色框里就是发送窗又。
SND.WND: 表示发送窗又的大小, 上图虚线框的格子数是 10 个,即发送窗又大小是 10 。
SND.NXT :下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。
SND.UNA: 一个绝对指针,它指向的是已发送但未确认的第一个字节的序列号。
接收方的滑动窗又包含三大部分,如下:
已成功接收并确认未收到数据但可以接收未收到数据并不可以接收的数据

蓝色框内,就是接收窗又。
REV.WND: 表示接收窗又的大小, 上图虚线框的格子就是 9 个。
REV.NXT: 下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。
40.了解 Nagle 算法和延迟确认吗?
Nagle 算法和延迟确认是干什么的?
当我们 TCP 报文的承载的数据非常小的时候,例如几个字节,那么整个网络的效率是很低的,因为每个 TCP 报文中都会有 20 个字节的 TCP 头部,也会有 20 个字节的 IP 头部,而数据只有几个字节,所以在整个报文中有效数据占有的比例就会非常低。

这就好像快递员开着大货⻋送一个小包裹一样浪费。
那么就出现了常⻅的两种策略,来减少小报文的传输,分别是:
Nagle 算法延迟确认
Nagle 算法
Nagle 算法:任意时刻,最多只能有一个未被确认的小段。所谓 “ 小段” ,指的是小于 MSS 尺寸的数据块,所谓 “ 未被确认” ,是指一个数据块发送出去后,没有收到对方发送的 ACK 确认该数据已收到。
Nagle 算法的策略:
没有已发送未确认报文时,立刻发送数据。
存在未确认报文时,直到「没有已发送未确认报文」或「数据⻓度达到 MSS 大小」时,再发送数据。
只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。
延迟确认
事实上当没有携带数据的 ACK ,它的网络效率也是很低的,因为它也有 40 个字节的 IP 头 和 TCP头,但却没有携带数据报文。
为了解决 ACK 传输效率低问题,所以就衍生出了TCP延迟确认。
TCP 延迟确认的策略:
当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK一般情况下,Nagle 算法和延迟确认不能一起使用,Nagle 算法意味着延迟发,延迟确认意味着延迟接收,两个凑在一起就会造成更大的延迟,会产生性能问题。
41.说说 TCP 的拥塞控制?
什么是拥塞控制?不是有了流量控制吗?
前面的流量控制是避免发送方的数据填满接收方的缓存,但是并不知道整个网络之中发生了什么。
一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时TCP就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大. ...所以,TCP 不能忽略整个网络中发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据流。
于是,就有了拥塞控制,控制的目的就是避免发送方的数据填满整个网络。
就像是一个水管,不能让太多的水(数据流)流入水管,如果超过水管的承受能力,水管会被撑爆(丢包)。

发送方维护一个**拥塞窗又 cwnd (congestionwindow )**的变量,调节所要发送数据的量。
什么是拥塞窗又?和发送窗又有什么关系呢?
拥塞窗又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(s lowstart
threshold )状态变量。当cwnd到达该阀值后,就好像水管被关小了水龙头一样,减少拥塞状态。即当cwnd>ssthresh时,进入了拥塞避免算法。
拥塞避免算法
一般来说,慢启动阀值 ssthresh 是 65535 字节,cwnd到达慢启动阀值后每收到一个 ACK 时,cwnd=cwnd+1/cwnd当每过一个 RT T 时,cwnd=cwnd+1显然这是一个线性上升的算法,避免过快导致网络拥塞问题。
接着上面慢启动的例子,假定 ssthresh 为 8 ::
当 8 个 ACK 应答确认到来时,每个确认增加 1/8 ,8 个 ACK 确认 cwnd 一共增加 1 ,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增⻓。

拥塞发生
当网络拥塞发生丢包时,会有两种情况:
RTO 超时重传快速重传如果是发生了RTO 超时重传,就会使用拥塞发生算法慢启动阀值 sshthresh=cwnd/2
cwnd 重置为 1进入新的慢启动过程

这种方式就像是飙车的时候急刹车,还飞速倒车,这。。。
其实还有更好的处理方式,就是快速重传。发送方收到 3 个连续重复的 ACK 时,就会快速地重传,不必等待RTO 超时再重传。
发生快速重传的拥塞发生算法:
拥塞窗又大小 cwnd=cwnd/2慢启动阀值 ssthresh=cwnd进入快速恢复算法
快速恢复
快速重传和快速恢复算法一般同时使用。快速恢复算法认为,还有 3 个重复 ACK 收到,说明网络也没那么糟糕,所以没有必要像 RTO 超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd 和 sshthresh 已被更新:
cwnd=cwnd/2 -sshthresh=cwnd然后,进入快速恢复算法如下:
cwnd=sshthresh+3重传重复的那几个 ACK (即丢失的那几个数据包)
如果再收到重复的 ACK ,那么 cwnd=cwnd+1如果收到新数据的 ACK 后, cwnd=sshthresh 。因为收到新数据的 ACK ,表明恢复过程已经结束,可以再次进入了拥塞避免的算法了。

42.说说 TCP 的重传机制?
重传包括超时重传、快速重传、带选择确认的重传(SACK )、重复 SACK 四种。

超时重传
超时重传,是 TCP 协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的 ACK 报文,那么就重新发送数据,直到发送成功为止。
超时时间应该设置为多少呢?
先来看下什么叫RT T (Round-TripTime ,往返时间)。

RT T 就是数据完全发送完,到收到确认信号的时间,即数据包的一次往返时间。
超时重传时间,就是 RTO (RetransmissionTimeout) 。那么,RTO 到底设置多大呢?
如果 RTO 设置很大,等了很久都没重发,这样肯定就不行。
如果 RTO 设置很小,那很可能数据都没有丢失,就开始重发了,这会导致网络阻塞,从而恶性循环,导致更多的超时出现。
一般来说,RTO 略微大于 RT T ,效果是最佳的。
其实,RTO 有个标准方法的计算公式,也叫Jacobson/Karels 算法。
- 首先计算 SRTT (即计算平滑的 RT T )
SRTT = (1 - α) * SRTT + α * RTT //求 SRTT 的加权平均
2.其次,计算 RTTVAR (round-trip time variation)
RTTVAR = (1 - β) * RTTVAR + β * (|RTT - SRTT|) //计算 SRTT 与真实值的差距- 最后,得出最终的 RTO
RTO = µ *SRTT+ ∂ *RTTVAR = SRTT+4 · RTTVAR在 Linux 下,α =0.125,β =0.25,μ =1,∂ =4。别问这些参数是怎么来的,它们是大量实践,调出的最优参数。
超时重传不是十分完美的重传方案,它有这些缺点:
当一个报文丢失时,会等待一定的超时周期,才重传分组,增加了端到端的时延。
当一个报文丢失时,在其等待超时的过程中,可能会出现这种情况:其后的报文段已经被接收端接收但却迟迟得不到确认,发送端会认为也丢失了,从而引起不必要的重传,既浪费资源也浪费时间。
并且,对于 TCP ,如果发生一次超时重传,时间间隔下次就会加倍。
快速重传
TCP 还有另外一种快速重传(FastRetransmit)机制,它不以时间为驱动,而是以数据驱动重传。
它不以时间驱动,而是以数据驱动。它是基于接收端的反馈信息来引发重传的。
可以用它来解决超时重发的时间等待问题,快速重传流程如下:

在上图,发送方发出了 1 ,2 ,3 ,4 ,5 份数据:
第一份 Seq1 先送到了,于是就 Ack 回 2 ;
结果 Seq2 因为某些原因没收到,S eq3 到达了,于是还是 Ack 回 2 ;
后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2 ,因为 Seq2 还是没有收到;
发送端收到了三个Ack=2的确认,知道了Seq2还没有收到,就会在定时器过期之前,重传丢失的Seq2。
最后,收到了 Seq2 ,此时因为 Seq3 ,S eq4 ,S eq5 都收到了,于是 Ack 回 6 。
快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2 、S eq3 、S eq4 、S eq5 呢?因为发送端并不清楚这连续的三个 Ack2 是谁传回来的。
根据 TCP 不同的实现,以上两种情况都是有可能的。可⻅,这是一把双刃剑。
为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。
带选择确认的重传(SACK)
为了解决应该重传多少个包的问题? TCP 提供了带选择确认的重传(即 SACK ,Selective
Acknowledgment )。
SACK 机制就是,在快速重传的基础上,接收方返回最近收到报文段的序列号范围,这样发送方就知道接收方哪些数据包是没收到的。这样就很清楚应该重传哪些数据包。

如上图中,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重发。
重复 SACK(D-SACK)
D-SACK ,英文是 DuplicateSACK ,是在 SACK 的基础上做了一些扩展,主要用来告诉发送方,有哪些数据包,自己重复接受了。
DSACK 的目的是帮助发送方判断,是否发生了包失序、ACK 丢失、包重复或伪重传。让 TCP 可以更好的做网络流控。
例如 ACK 丢包导致的数据包重复:

接收方发给发送方的两个 ACK 确认应答都丢失了,所以发送方超时后,重传第一个数据包(3 000~ 3499 )
于是接收方发现数据是重复收到的,于是回了一个SACK=3000~3500,告诉「发送方」
3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已收到,所以这个 SACK 就代表着 D-SACK 。这样发送方就知道了,数据没有丢,是接收方的 ACK 确认报文丢了。
43.说说 TCP 的粘包和拆包?
TCP 的粘包和拆包更多的是业务上的概念!
什么是 TCP 粘包和拆包?
TCP 是面向流,没有界限的一串数据。TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行
发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。

为什么会产生粘包和拆包呢?
要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包;
接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;
要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包;
待发送数据大于 MSS (最大报文长度),TCP 在传输前将进行拆包。即 TCP 报文长度 -TCP 头部长度 >MSS 。
那怎么解决呢?
发送端将每个数据包封装为固定长度在数据尾部增加特殊字符进行分割将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。