面向连接
两个使用 TCP 的应用,在彼此交换数据之前必须先建立一个 TCP 连接
基于字节流
TCP 连接双方的数据交换格式是以字节 (byte,1byte = 8 bit)构成的有序但无结构的字节流。TCP 不在字节流中插入记录标识符
报文格式
-
端口号 (Source Port and Destination Port):
-
序号 (Sequence Number):这个字段的主要作用是用于将失序的数据重新排列。TCP 会隐式地对字节流中的每个字节进行编号,而 TCP 报文段的序号被设置为其数据部分的第一个字节的编号。序号是 32 bit 的无符号数,取值范围是0到 232 - 1。
-
确认序号 (Acknowledgment Number):接收方在接受到数据后,会回复确认报文,其中包含确认序号,作用就是告诉发送方自己接收到了哪些数据,下一次数据从哪里开始发,因此,确认序号应当是上次已成功收到数据字节序号加 1。只有 ACK 标志为 1 时确认序号字段才有效。
-
首部长度 (Header Length):首部中的选项部分的长度是可变的,因此首部的长度也是可变的,所以需要这个字段来明确表示首部的长度,这个字段占 4 bit,4 位的二进制数最大可以表示 15,而首部长度是以 4 个字节为一个单位的,因此首部最大长度是 15 * 4 = 60 字节。
-
保留字段 (Reserved):占 6 位,未来可能有具体用途,目前默认值为0.
-
控制位 (Control Bits):在三次握手和四次挥手中会经常看到 SYN、ACK 和 FIN 的身影,一共有 6 个标志位,它们表示的意义如下:
- URG (Urgent Bit):值为 1 时,紧急指针生效
-
ACK (Acknowledgment Bit):值为 1 时,确认序号生效
- PSH (Push Bit):接收方应尽快将这个报文段交给应用层
- RST (Reset Bit):发送端遇到问题,想要重建连接
-
SYN (Synchronize Bit):同步序号,用于发起一个连接
-
FIN (Finish Bit):发送端要求关闭连接
-
窗口大小 (Window): TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个 16 bit 字段,单位是字节, 因而窗口大小最大为 65535 字节。
-
检验和 (Checksum):功能类似于数字签名,用于验证数据完整性,也就是确保数据未被修改。检验和覆盖了整个 TCP 报文段,包括 TCP 首部和 TCP 数据,发送端根据特定算法对整个报文段计算出一个检验和,接收端会进行计算并验证。
-
紧急指针 (Urgent Pointer):当 URG 控制位值为 1 时,此字段生效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
-
选项 (Options):这一部分是可选字段,也就是非必须字段,最常见的可选字段是“最长报文大小 (MSS,Maximum Segment Size)”。
-
有效数据部分 (Data):这部分也不是必须的,比如在建立和关闭 TCP 连接的阶段,双方交换的报文段就只包含 TCP 首部。
可靠性
- 合理的数据大小:TCP 发送的数据并不是固定的大小,而是会根据实际情况调整报文段的大小。
- 检验和:接收端发送端会以同样的方式计算校验和,如果不一致,说明报文段出现错误,会将其丢弃。
- acknum和seqnum:对乱序的数据进行排序后发给应用层,并丢弃重复的数据。
- 超时重传机制:当 TCP 发出一个报文段后,它会启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段
- 连接管理:也就是三次握手和四次挥手,连接的可靠性是整体可靠性的前提,本文第二部分将会详细介绍连接管理的内容。
- 流量控制:TCP 双方都有固定大小的缓冲区,流量控制的原理是利用滑动窗口控制数据发送速度,避免缓冲区溢出导致数据丢失。
- 拥塞控制:TCP 利用慢启动和拥塞避免等算法实现了拥塞控制。
和 UDP 的区别
| UDP | TCP | |
|---|---|---|
| 是否连接 | 无连接 | 面向连接 |
| 是否可靠 | 不可靠,没有确认机制、流量控制和拥塞控制 | 可靠,有确认机制、流量控制和拥塞控制 |
| 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只支持一对一通信 |
| 传输方式 | 面向报文 | 面向字节流 |
| 首部开销 | 首部开销小,固定8字节 | 首部开销较大,最小20字节,最大60字节 |
| 适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,如文件传输等 |
TCP三次握手流程
- 第一次握手,SYN=1, seq=x(初始序号ISN,可以随机选择),客户端进入 SYN_SEND 状态,等待服务端确认,在此之前,服务端处于监听状态
- 第二次握手,SYN=1, ACK=1, seq=y, ack=x+1(服务器期望下次收到客户端的数据的序号),服务器端进入 SYN_RCVD 状态,等待客户端确认
- 第三次握手,ACK=1,ack=y+1,seq = x + 1发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。
为什么握手不能是两次
-
防止已过期的连接请求报文突然又传送到服务器,因而产生错误
- 客户端先发了请求连接A,没收到回复,重发的B后发先到
-
三次握手才能让双方均确认自己和对方的发送和接收能力都正常,
(在收到确认之前,不确定自己的发送能力)
- 第一次客户端发起,s知道c能发,s能收
- 第二次服务端发起,c知道s能发能收,c确认自己能发能收
- 第三次客户端发起,s知道c能发能收,s确认自己能发能收
-
告知对方自己的初始序号值,并确认收到对方的初始序号值
TCP 报文段中维护了序号字段和确认序号字段,也通过这两个字段双方都可以知道在自己发出的数据中,哪些是已经被对方确认接收的
第三次握手,服务端才能确认自己的初始序号(c的接收能力)
为什么握手不能是四次
因为三次握手已经可以确认双方的发送接收能力正常
TCP 建立连接机制的漏洞怎么解决?
服务器在收到了客户端的 SYN 报文段后,会分配并初始化连接变量和缓存,并向客户端发送 SYN + ACK 报文段,这相当于是打开了一个“半开连接 (half-open connection)”,会消耗服务器资源。
在 SYN 洪泛攻击中,攻击者发送大量的 SYN 报文段到服务器请求建立连接,但是却不进行第三次握手,这会导致服务器打开大量的半开连接,消耗大量的资源,最终无法进行正常的服务。
解决方法:SYN Cookies
利用特定散列函数计算出一个 cookie 值。这个 cookie 作为将要返回的SYN + ACK 报文段的初始序列号(ISN)。当客户端返回一个 ACK 报文段时,服务器根据首部字段信息计算 cookie,与返回的确认序号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后分配资源并建立连接,否则拒绝建立连接。
四次挥手详细过程
TCP 连接是全双工 (即数据在两个方向上能同时传递)
因此每个方向必须单独地进行关闭。
当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向连接。当一端收到一个 FIN,它必须通知应用层另一端已经终止了数据传送。
详细过程如下:
-
客户端发送关闭连接的报文段,FIN 标志位1,请求关闭连接,并停止发送数据。seq = x = 之前发送的所有数据的最后一个字节的序号加一,客户端FIN-WAIT-1,等待来自服务器的确认报文。
-
服务器收到 FIN 报文后,发回确认报文,ACK = 1, ack = x + 1,并带上自己的序号 seq = y,然后服务器就进入 CLOSE-WAIT 状态。
服务器还会通知上层的应用程序对方已经释放连接,此时 TCP 处于半关闭状态,也就是说客户端已经没有数据要发送了,但是服务器还可以发送数据,客户端也还能够接收。
-
客户端收到服务器的 ACK 报文段后随即进入 FIN-WAIT-2 状态,此时还能收到来自服务器的数据,直到收到 FIN 报文段。
-
服务器发送完所有数据后,会向客户端发送 FIN 报文段,各字段值如图所示,随后服务器进入 LAST-ACK 状态,等待来自客户端的确认报文段。
-
客户端收到来自服务器的 FIN 报文段后,向服务器发送 ACK 报文,随后进入 TIME-WAIT 状态,等待 2MSL(2 * Maximum Segment Lifetime,两倍的报文段最大存活时间)
这是任何报文段在被丢弃前能在网络中存在的最长时间,常用值有30秒、1分钟和2分钟。如无特殊情况,客户端会进入 CLOSED 状态。
-
服务器在接收到客户端的 ACK 报文后会随即进入 CLOSED 状态
为什么 TCP 关闭连接为什么要四次而不是三次
服务器在收到客户端的 FIN 报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会做出应答(为什么立即应答?因为不立即应答客户端不知道自己的请求是否送到),返回 ACK 报文段,接下来可能会继续发送数据,在数据发送完后,服务器会向客户单发送 FIN 报文,表示数据已经发送完毕,请求关闭连接,然后客户端再做出应答,因此一共需要四次挥手。
客户端为什么需要在 TIME-WAIT 状态等待 2MSL 时间才能进入 CLOSED 状态?
如果客户端发送的 ACK 报文段丢失,服务器在接收不到 ACK 的情况下会一直重发 FIN 报文段
如果在这段时间内没有收到来自服务器的 FIN 报文,那就说明服务器已经成功收到了 ACK 报文,此时客户端就可以进入 CLOSED 状态了。
流量控制
TCP 连接双方的主机都为该连接设置了发送缓存和接收缓存,利用发送缓存将其缓存起来,然后再按一定的速率通过网络发送给对方,而接收缓存的作用是把对方传来的数据先缓存起来,等到己方应用程序有空的时候再来取走数据。
发送>接收缓存===丢失
解决方案--早期:停止-等待模式 (stop-wait)
发送方在发送数据包 (图中的msg)时会设置一个计时器,然后等待接收方的 ACK,接收方在收到数据后会返回 ACK 作为应答,发送方在收到 ACK 后会发送下一个数据包。
如果由于网络原因造成数据包或者 ACK 丢失时,计时器会超时,然后发送方会重新发送未被确认的数据包。这种模式虽然可以确保数据传输的可靠性,效率太低
滑动窗口
发送窗口
1、接收方的提供的窗口大小 (TCP 报文段首部中的 window 字段),发送方在三次握手阶段首次得到这个值,之后的通信过程中接收方会根据自己的可用缓存对这个值进行动态调整;
2、发送方会根据网络情况维护一个拥塞窗口变量 (后文介绍)。发送窗口的大小取这两个值的最小值。对于发送方来说,发送窗口分为两部分,分别是已经发送的部分(已经发送了,但是没有收到ACK)和可用窗口,接收端允许发送但是没有发送的那部分称为可用窗口。
接收窗口
- Received and ACK Not Send to Process:这部分数据属于接收了数据但是还没有被上层的应用程序接收;
- Received Not ACK: 已经接收,但是还没有回复 ACK;
- Not Received:有空位,还没有被接收的数据。
滑动窗口是如何滑动
累计确认:如果发送方发了包1,包2,包3,包4;接受方成功收到包1,包2,包3。那么接受方可以发回一个确认包,序号为4(4表示期望下一个收到的包的序号)
那么发送方就知道包1到包3都发送接收成功,必要时重发包4。
一个确认包确认了累积到某一序号的所有包,而不是对每个序号都发确认包。
具体到TCP,它对字节编号。比如发送方发了包1,包2,包3;包1含字节0到10,包2含字节11到20,包3含字节21到30。接受方成功收到包1,包2。那么接受方发回一个包含确认序号21的包,发送方就知道字节0到20(包1,包2)都成功收到,必要时要重发的只需从字节21开始。
流水线传输
客户端和服务器建立了一条TCP连接,同时客户端需要向服务器发送一个大文件
由于网络对数据报长度的限制,TCP会将文件分割成很多的数据段,然后从前往后逐一发送,先发送第一个TCP数据段,当接收到服务器的确认报文后,再发送第二段,以此类推
为了解决上面所述的问题,才有了流水线传输。流水线传输的原理很简单:连续发送多个数据段,而不需要先等待之前发送的数据段被确认。
第一段说的情况中,最大的浪费就是等待ACK到达的这段时间,客户端什么事情都干不了,而流水线就是为了解决这个问题。同样是一份文件,被分成了多个数据段,假设客户端连续发送3个数据段,被服务器确认接收后,再发送3个,以此类推。因为合理利用了等待的时间
流水线传输将要面临的问题
回退N步GBN
- 接受方每次返回 累积最大 n,表示之前的都被成功接收了
- 如果返回的不是n,发送方将重传
n,n+1,n+2等所有已经发送但还没有接收到ACK的报文
维护一个大小为N的窗口来限制数据的发送,这个窗口其实就是一个区间,而序号落在这个区间内的报文段,都可以不需要等待之前的报文段被确认而直接发送。
在GBN中,我们可以将所有的报文段序号划分为四个区间:
- 已经发送并接收到
ACK报文的报文段; - 已经发送但是还没有接收到
ACK报文的报文段; - 允许被发送,但是还没有发送的报文段;
- 还不能发送的报文段;
base:第一个被发送,但是还没有接收到ACK的报文段的序号
nextseqnum:下一个需要被发送的报文段的序号(可用,但还未发送)
base和nexts,窗口大小N将发送方序列分割为上面四个部分
对于发送方:
-
上层调用接口向网络中传输数据。
发送方将检查窗口中是否有剩余的空间,即窗口中是否包含序号属于第三个区间的报文段。若窗口有空闲,则将数据发出,同时让
nextseqnum + 1若此时窗口中的所有数据段都是已经发送但还没有接收到
ACK的数据段,则发送方可以将数据返回给上层,隐式地告诉上层窗口已满,或者将上层传递的数据放入缓存,等窗口有空闲区间之后再将数据发送 -
接收到一个
ACK报文当收到一个
ACK报文时,窗口将向前移动,直到base的值等于这个ACK报文所确认的下一个报文的序号为止 -
发生超时事件
在
GBN中,只有一个计时器,而它记录的是序号为base的报文是否超时,当这个报文段被发出后,超过了指定时间还没有收到ACK报文(表明大概率已经丢失)
这里在补充一下发送方的计时器,当接收到一个ACK后,若还存在已经发送但是没有收到ACK的报文段,计时器将重新启动;若不存在,则停止,并等到有数据发出后再启动。
假设接收方已经成功接收到了报文段0到n-1,此时收到报文n,这是一条按顺序到达的报文,所以接收方直接将数据段交付给上层;
但是若收到的不是n,而是n+1,甚至更大,则接收方会将它丢弃,并向发送方回送n-1的ACK报文,表示它目前成功接收到的最大的报文段是n-1(被丢弃的不算成功接收),而发送方在超时后,将重传n,n+1
接收方每次确认的都是当前已经接收,并且按序到达的最大值。
为什么接收方接收到乱序的报文后,不可以先将它缓存下来,等序号更小的报文段到达后,再一同传递给上层?那是因为没有太大的必要性。
假设报文n丢失,发送方将重传n,n+1,n+2等所有已经发送但还没有接收到ACK的报文,所以接收方就没有必要再缓存之前乱序到达的报文段了。
而对于乱序到达的报文,有不小的几率是前面的报文已经丢失。尤其是乱序到达的越多,之前的那些报文丢失的几率越大。
选择重传SR
- 接收方维护了一个变量
rcv_base(最小未收到的的序号),发过来的,大于base,小于窗口就缓存,等于base窗口移动,小于base抛弃,每次都返回ack - 发送方只重传出错帧,维护了一个变量
send_base收到ack,等于base就移动窗口,小于base忽略,大于base标记为已收到
GBN最大的一个缺陷就是,发生丢包事件时,可能出现很大程度的无用功。比如说发送方同时发送出了10个报文段,但是第一个报文段丢失,结果将导致这10个报文段都要重传,就算后面9个报文段成功到达,也会被接收方丢弃。而选择重传机制解决了这个问题,因为它将有选择地重传报文段。
SR也维护一个大小为N的窗口
只有序号落在窗口中的报文段才允许被发送。但是和GBN不同,SR的窗口中报文段不会被按顺序确认,后发出的报文也可以被先确认。
-
上层调用数据传输接口,请求发送数据。
- 发送端将检查窗口中是否还有空闲空间,即窗口中是否还存在可用,但是还没有使用的序号。
- 若存在,则使用
nextseqnum指向的序号封装报文段并发送。同时nextseqnum指针向前移动,直到没有空间或者数据发送完毕为止 - 若不存在空闲序号,则发送方可以将数据返回给上层,隐式地告知上层当前发送接口不可用,可以稍后再试,或者也可以将数据缓存,当窗口有空闲时再发送,或者使用同步机制。
-
接收到某个报文段的
ACK报文。- 接收到一条已经被确认的报文段的重复
ACK(一般是由超时引起),则忽略它; - 收到一条序号不是
send_base的报文段的ACK,则标记这个序号的报文已经被确认; - 收到序号为
send_base的报文的ACK报文,此时send_base指针向前移动,直到移动到第一个已经发送但是还没有被确认的报文序号为止
- 接收到一条已经被确认的报文段的重复
-
报文超时。由于在
SR中,报文可以无序到达,所以在理论实现中,需要为每一个已经发送的报文段都绑定一个计时器,来记录此报文是否超时。若某个已经发送的报文段的计时器超时,发送方将重传对应的报文,注意,和
GBN不同,SR只重传超时的那条报文。
在SR机制中,发送方若接收到一个乱序报文段,不会将其丢失,而是放入到接收缓存中,然后等待序号更小的报文段都到达后,再取出交付给上层。所以对于接收方来说,报文序号分为四类:
- 期待接收到的报文段,即下一条想要的报文段;
- 已经接收,但不是按顺序到达报文段;
- 可以接收的报文段;
- 暂时还不能接收的报文段;
接收方也是通过维护一个大小为N的窗口来管理这些报文段
在接收方维护了一个变量rcv_base,表示窗口的第一个报文段的序号,其实也就是下一条期待接收的报文。
- 接收到的报文段的序号为
rcv_base。此时rcv_base将向前移动,直到到达第一个没有被接收的序号为止,这样意味着窗口在向前移动,并腾出了新的空闲区间; - 接收到的报文序号在窗口中,但不是
rcv_base。此时,若这个报文之前已经接收过,则直接发送此报文对应的ACK报文,但是不接收;若没有接收过,则将此报文放入接收缓存中,再发送ACK报文; - 接收到的报文序号小于
rcv_base,则表示这条报文已经被接收过,直接发送一个ACK报文,但是不接收此报文段; - 除上述外,其他报文段都忽略;
为什么接收方会接收到已经接收过的报文段:ACK报文超时到达接收方,或者ACK报文丢失,所以导致了发送方对报文段进行重传。
TCP的流水线传输
实际的TCP流水线传输,是对这两种模型的结合和改进,抽取了各自好的部分。
在TCP的流水线传输机制中,和GBN一样使用的是累计确认,同时只使用一个计时器,用来记录窗口中的第一个报文段是否超时,同时只重传丢失的分组
-
对于接收方:接收到不按照顺序到达的报文段,不会丢弃,而是放入接收缓存。
- 接收到按序到达的报文(序号为
rcv_base的报文),窗口移动到第一个没收到前,然后返回ack - 接收到的报文序号不是
rcv_base,但是依旧落在窗口内,则发送确认报文,但是是对rcv_base - 1这个报文的确认,表示现在接收方已经接收到的按序的报文中,最大值是rcv_base - 1,加入缓存
- 接收到按序到达的报文(序号为
-
对于发送方:TCP的计时器记录的是序号为
base的数据段是否超时,若发生超时事件时,发送方只会重传序号为base的报文段,而不会重传后续的这些已经发送但还没有接收到ACK的报文段。
由于TCP的流水线传输使用的是累计确认,所以没有必要每接收到一条报文,都进行一次确认,而是有选择地进行确认,这样可以减少传输ACK报文的数量,节省网络资源。 而在TCP规范中,对于何时传输ACK,给出了几条建议:
- 接收到序号为
rcv_base的报文段时,若这之前的报文都已经确认过了,则延迟ACK报文的发送,等待另一个报文段的到达(最多等待500毫秒) - 接收到序号为
rcv_base的报文段时,若在这之前还有没被确认的报文(rcv_base后面有先到的),则立即发送一个ACK - 接收到一个序号比
rcv_base大的报文段,表示出现了乱序,此时立即发送一个ACK报文,告知发送方下一条需要的是rcv_base; - 若接收到的报文段序号是
rcv_base,同时之前已经接收了一些乱序的报文段,则立即发送ACK报文;
对于发送方接收ack,超时重传定时器溢出之前,接收到连续的三个重复冗余ACK(或者多个),发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段
快速重传
若发送方接收到对同一条报文的三次冗余确认(也就是四次确认),就认为这条报文的下一条已经丢失,于是不管计时器是否超时,都直接重传这条报文的下一条。
为什么是三次冗余ack(而不是更少)
发送有序,到达可能乱序
窗口大小的限制
窗口长度必须小于等于总区间长度的一半。因为发送窗口的头和接收窗口的尾一定是重合的,而为了让发送窗口的尾不碰到接收窗口的头,必须使它们两个的长度之和小于等于总区间长度。
拥塞控制
什么是拥塞控制
网络层中路由器不是直接就可以转发的,它们需要先将接收到的数据放入自己的内存(可能还要做一些处理),再从中取出进行转发。
路由器的内存是有限的,若同一时间到达某个路由器的数据太多,这个路由器将无法接收所有的数据,只能将一部分丢弃;或者同一台路由器数据太多,后面到达的数据将要等待较长的时间才会被转发。
网络拥塞-->高频重传-->更拥塞,不行
拥塞控制,就是在网络中发生拥塞时,减少向网络中发送数据的速度,防止造成恶性循环;同时在网络空闲时,提高发送数据的速度,最大限度地利用网络资源。
拥塞控制的方法
-
端到端拥塞控制
由发送数据的端系统自己来判断是否拥塞,然后调整传输速率。
- 发送的数据已经超时却还没有接收到确认报文
- 数据往返时延过高
- 接收到对同一个数据段的重复确认
-
网络辅助的拥塞控制
- 路由器直接向发送端发送报文,告知网络拥塞情况
- 路由器更改数据段中的某个标志,来提示网络中的拥塞情况,然后数据将这个标志携带到目的主机,再由目的主机根据这个标志,向发送端发送报文,告知拥塞情况(被包含在确认报文中);
TCP的拥塞控制方法
TCP协议采用的是第一种方式——自己判断网络的拥塞情况
当TCP检测到网络拥塞,则降低数据的发送速率,否则增加数据的发送速率
-
TCP如何限制数据的发送速率限制窗口的大小,拥塞窗口被称为
cwnd在
TCP发送端,所有被发送但是还没收到确认的数据段必须落在这个窗口中,所有,当网络拥塞时,TCP程序将减小cwnd,而网络通畅时,增大cwnd,以此来控制数据发送的速率。 -
TCP如何检测网络中是否拥塞- 发送一条数据段后,成功接收到了接收方的确认报文,则可以认为网络没有拥塞;
- 发送出一条数据段后,在规定时间内没有收到确认报文(丢失或时延太大),则可以认为网络出现了拥塞
- 连续收到接收方对同一条报文的三次冗余确认(也就是四次确认,冗余ack),则可以推测那条报文丢失,即发生了拥塞;
-
TCP根据什么算法来调整速率主要算法有 慢启动 , 拥塞避免 以及 快速恢复
慢启动
- MSS:最大报文段长度,
TCP双方发送的报文段中,包含的数据部分的最大字节数; - cwnd:拥塞窗口,
TCP发送但还没有得到确认的报文的序号都在这个区间; - RTT:往返时间,发送方发送一个报文,到接收这个报文的确认报文所经历的时间;
- ssthresh:慢启动阈值,慢启动阶段,若
cwnd的大小达到这个值,将转换到拥塞避免模式;
cwnd通常被初始化为1MSS,这个值比较小,在这个时候,网络一般还有足够的富余,而慢启动的目的就是尽快找到上限。在慢启动阶段,发送方每接收到一个确认报文,就会将cwnd增加1MSS的大小
- 初始
cwnd=1MSS,所以可以发送一个TCP最大报文段,成功确认后,cwnd = 2MSS; - 此时可以发送两个
TCP最大报文段,成功接收后,cwnd = 4 MSS; - 此时可以发送四个
TCP最大报文段,成功接收后,cwnd = 8 MSS.....
由于TCP是一次性将窗口内的所有报文发出,所以所有报文都到达并被确认的时间,近似的等于一个RTT,拥塞窗口cwnd的长度将在每个RTT后翻倍
过程什么时候改变呢,这又分几种情况:
-
在慢启动的过程中,发生了数据传输超时
则此时
TCP将ssthresh的值设置为cwnd / 2,然后将cwnd重新设置为1MSS,重新开始慢启动过程,这个过程可以理解为试探上限; -
若
cwnd的值增加到>= ssthresh时(在第一种情况触发前,是不知道ssthresh的)此时若继续使用慢启动的翻倍增长方式可能有些鲁莽,所以这个时候结束慢启动,改为拥塞避免模式;
-
若发送方接收到了某个报文的三次冗余确认(触发快速重传)则进入到快速恢复阶段;同时,
ssthresh = cwnd / 2,毕竟发生快速重传也可以认为是发生拥塞导致的丢包,然后cwnd = ssthresh + 3MSS;
拥塞避免
刚进入这个模式时,cwnd的大小近似的等于上次拥塞时的值的一半(ssthresh)(这是由进入这个模式的条件决定的)
拥塞避免是一个速率缓慢且线性增长的过程,每经历一个RTT(请注意2.4中有关RTT的结论),cwnd的大小增加1MSS
线性增长的过程什么时候结束,分为两种情况:
- 在这个过程中,发生了超时,则表示网络拥塞,这时候,
ssthresh被修改为cwnd / 2,然后cwnd被置为1MSS,并进入慢启动阶段; - 发送方接收到了某个报文的三次冗余确认(即触发了快速重传的条件),此时也认为发生了拥塞,则,
ssthresh被修改为cwnd / 2,然后cwnd被置为ssthresh + 3MSS,并进入快速恢复模式;
快速恢复
快速恢复阶段,每接收到一个冗余的确认报文,cwnd就增加1MSS,其余不变,而当发生以下两种情况时,将退出快速恢复模式:
- 在快速恢复过程中,计时器超时,这时候,
ssthresh被修改为cwnd / 2,然后cwnd被置为1MSS,并进入慢启动阶段; - 若发送方接收到一条新的确认报文(不是冗余确认),则
cwnd被置为ssthresh,然后进入到拥塞避免模式;
- 慢启动:(一旦timeout,进入)
- 拥塞避免:在慢启动状态到达ssthresh(慢启动阈值),或者在快速恢复接收到新ACK,将cwnd设为ssthresh(正常了,从ssthresh开始)
- 快速恢复:触发了快速重传(三次冗余ack后)进入,cwnd从ssthresh+3MSS开始,ssthresh设为cwnd/2,递增直到超时或收到新ack