本系列基于哈工大李全龙老师的课程
目录
一.详解TCP协议
1.1TCP概述
1.2TCP段结构:
在传输层处理的是一个个的TCP段Segment。
TCP段里的序列号和ACK号不是段的编号,而是利用数据的字节数来计数得到的。
- 源端口、目的端口:发送方和接收方的端口号
- 序号:TCP连接中的字节流都有编号。如果101开始,传送了100个字节,那么下次就是从201开始
- 确认序号:接送方发给发送方下次需要的字节序号
- 首部长度:就是TCP首部的长度,记录了TCP报文数据到TCP报文段开始的距离
- 保留:留待以后使用
- URG:紧急字段 1-代表有紧急数据 0-代表没有
- ACK:在TCP报文段中,字段ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1
- PSH:当两个应用在进行交互时,如果想要立马得到对方的回复就PSH设置为1
- RST:RST为1时代表需要重新建立连接
- SYN:在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
- FIN:用来释放连接;当FIN=1时代表终止连接
- 窗口大小:用来进行流量控制
- 校验和:对整个TCP报文段进行校验和计算,由目标端进行校验
- 紧急指针:记录一个偏移量,指向紧急数据的最后一位(也可以是紧急数据的下一位,两者都是标准),在读取到紧急指针所指向的位置之前,TCP的接受进程都处于紧急状态,当读取到紧急数据后一位时,回复到正常状态。
1.3TCP序列号和ACK
Q:接收方如何处理乱序到达的Segmen?
A:
TCP规范中没有规定,由TCP的实现者做出决策。
比如在GBN协议中,对于乱序到达的Segment会丢弃,而在SR协议中,乱序到达的Segment会被缓存起来。
远程登录的示例:
上图中,在发送数据前,主机A和B已经建立连接并交换过信息,因此,这里初始的Seq=42和Ack=79都是之前建立连接时随机选取的初始序号。
- 在主机A向主机B发送数据C时,该Segment的初始序列号为42,期望收到的下个ACK的序号为79。
- 主机B接收到数据C后,会返回ACK确认,此时的Segment段的序列号为79,对应于上一个Segment的期望ACK。而段的ACK值为43,是因为本次发送的数据C是一个字符,只有一个字节,因此接收方期望的下一个段的开始字节应该是43。
- 主机A收到ACK后,还要再发一个ACK确认,此时不携带任何数据。序列号是43,对应于上一个ACK=43。ACK=80,对应于上一个Seq=79,表示发送方期望的下一个序列号是80开始。
1.4TCP可靠数据传输
- 概述:
- TCP RTT和超时
- TCP发送方事件
- TCP发送端程序的伪代码
首先初始化一个SendBase和NextSeqNum变量,用于标识发送端滑动窗口的相关参数。接下来是一个无限循环的过程,用switch方法区分不同的event事件。
- 如果event事件是接收从上层应用层传递的数据,则创建该数据对应的TCP Segment段,其中Seq序列号即为最初初始化的NextSeqNum。如果不存在定时器,则启动定时器。将段传递到IP层。下一个NextSeqNum = NextSeqNum+该段传输数据的字节大小。
- 如果发生的是timeout事件:重传具有最小序列号的,并且还没有被确认过的的段Segment。并重启启动定时器
- 如果发生的是ACK确认:如果ACK确认的序列号是大于SendBase的,意味着有新的数据被确认了,由于TCP采用的是累计确认的机制,接收到ACK序列号y表示序列号y之前的Segment都被确认了。因此,更新SendBase = y。如果还有未被确认的Segment,则重启启动定时器。
- TCP重传示例
- 快速重传机制
伪代码
Q:为什么是接收三次同一个数据的ACK就进行重传?
A:接收三次同一个数据的 ACK 进行重传是为了确保数据的可靠传输,避免 ACK 丢失引起的错误,以及处理网络延迟和乱序。这样可以提高数据传输的可靠性和准确性。
A:三次是怎么来的呢? ACK是累积确认机制,如果在传输过程中有一个数据包丢失了,而其后面的数据包是正常发送的,那么接收端只返回确认了丢失的数据包之前的分组编号,所以会产生三次重复的ACK
1.5TCP流量控制
接收方为TCP连接分配buffer(缓冲区)
接收分配的 buffer 是接收方在接收数据时所分配的内存空间,用于存储接收到的数据包,并按照序号组装成完整的数据。它的大小是有限的,需要及时释放空间,以便接收更多的数据包。
左边是有数据从IP层发送过来,然后将数据放到RcvBuffer缓存里,最后向上交付到应用层。蓝色部分表示目前接收端可以用来接收buffer部分。
此时,如果上层应用处理buffer中数据的速度较慢,而发送方发送的数据速率过快,就会导致接收方没有那么多的缓存大小来接收数据,导致buffer缓存溢出。
因此,流量控制的目的就是让发送方不会传输的太多、太快以至于淹没接收方(buffer溢出)。
本质上,流量控制是一场速度匹配机制。
由上图可知,Buffer中的可用空间(spare room)
= RcvWindow
= RcvBuffer - [LastByteRcvd - LastByteRead]
Receiver通过在Segment的头部字段(TCP段的Receive Window字段)将RcvWindow告诉Sender,Sender限制自己已经发送的但还未收到ACK的数据不超过接收方的空闲RcvWindow尺寸。
假设接收方Buffer已经满了,Receiver告知Sender RcvWindow=0,会出现什么情况?
当RcvWindow=0时,即接收方缓存满了,要求发送方不能发送数据了。但是这种情况下,发送方和接收方就无法交互了,也就是说,当后续接收方即使有了空余的buffer,也无法再发送给接收端告知它继续发送数据了。
因此,对于这种情况,需要一些额外的处理。即使RcvWindow=0,在TCP中,发送端仍然可以发送一个很小的一个段Segment,从而带回来接收端的最新buffer大小,以此来解决上述问题。
1.6TCP连接管理——“三次握手”
初始化TCP变量,创建Socket等待连接
- 三次握手
- 客户端向服务器发送SYN包(SYN=1,ACK=0),表示请求建立连接,并随机生成一个初始序列号(sequence number)x。
- 服务器收到SYN包后,向客户端发送一个SYN/ACK包(SYN=1,ACK=1),表示确认客户端的请求,并随机生成一个初始序列号y,同时将确认号(acknowledgment number)设置为x+1。这个ACK表示服务器已经成功接收到了客户端的请求,并且告诉客户端,服务器准备好了,可以开始发送数据了。
- 客户端收到服务器的SYN/ACK包后,向服务器发送一个ACK包,确认收到了服务器的确认,并将确认号设置为y+1。这个ACK表示客户端已经成功接收到了服务器的确认,并且告诉服务器,客户端准备好了,可以开始发送数据了。
Q:为什么是三次握手?两次不行吗?
A:
三次握手是为了确保双方的通信能够正常进行,防止因网络延迟或丢包等原因导致连接建立失败。如果只进行两次握手,可能会出现以下问题:
网络延迟导致的连接失败:在两次握手的情况下,如果第一次握手的SYN包由于网络延迟而丢失,那么服务器将无法收到客户端的请求,导致连接建立失败。
重复连接请求导致的混乱:在两次握手的情况下,如果第一次握手的SYN包丢失,但第二次握手的SYN/ACK包却成功到达客户端,那么客户端会误认为连接已经建立成功,而服务器并不知道这个连接,导致双方的状态不一致,无法进行正常的数据传输。
TCP连接示意图:
TCP连接关闭:
TCP连接客户端的生命周期
TCP连接服务器端的生命周期
二.拥塞控制原理
2.1拥塞控制
- 成因
多跳的网络:
Q:随着λin和λin’不断增加,会怎么样?
如上图所示,以路由器R2举例来说,承载着主机A与主机C的信息交互,以及主机B和主机D的信息交互,这两个不同的链路间在R2路由器上存在资源竞争,也同样会导致R2的缓存排队、丢包等问题,导致重发,进一步造成网络拥塞,路由器吞吐率降低。
而这种场景跟上面场景的最大区别在于,当数据到达R2路由器时,说明该数据已经被上一跳的路由器存储转发处理完了。然后,在R2时,由于拥塞导致分组被丢掉了,此时上一跳路由器的对数据的处理也是被浪费掉了。
所以,在多跳网络中,拥塞会造成另一个代价:
- 当分组被丢弃时,任何用于该分组的“上游”传输能力全都被浪费掉了。
- 造成了网络性能、吞吐率更差了
- 拥塞控制的方法
2.2TCP的拥塞控制
2.2.1基本原理
CongWin表示拥塞窗口的大小,计算发送的最后一个字节的序列号 减去 最后一个接收到的ACK字节的序列号,这个计算的差值即表示当前网络中正在传输的数据大小。 让计算得到的差值小于设置的拥塞窗口大小。
PS:我们可以将路由器抽象成网络窗口,而拥塞窗口就是网络窗口中的可用缓存大小。
这种拥塞控制的是已发送但还未收到ACK的数据量大小,因此粗略来讲,在一个RTT时间内,会发送CongWin窗口大小的数据量,因此发送速率的计算就是拥塞窗口的大小 除以 RTT的大小。
Q:如何感知网络发生拥塞?
A:
- loss事件:timeout 或者收到3个重复的ACK(至于为什么会收到3个重复的ACK上文有说)
- 发生loss事件后,发送方降低速率(流量控制)
Q:如何合理的调整发送速率?
A:
- 加性增——乘性减:AIMD
- 慢启动:SS
2.2.2加性增——乘性减:AIMD
AIMD:
- Additive Increase:每个RTT将CongWin增大一个MSS(指最大段长度)—拥塞避免思想
- Multiplicative Decrease:发送loss后将CongWin减半
2.2.3慢启动——SS、快重传、快恢复
- TCP连接建立时,初始化CongWin=1,例如:
- MSS = 500 byte,RTT = 200ms
- 初始速率 = 20 kbps
- 由于初始速率比较小,可用带宽可能远远高于初始速率:
- 如果开始阶段还是使用线性增长的话,可能需要增长很长一段时间才能增长到可用带宽,存在资源浪费
- 因此,希望在开始阶段快速增长
快速增长示例:
Q:快速增长和线性增长有何联系?
A:当CongWin达到Loss事件的1/2时
实现方法:
- 需要定义一个Threshold变量。
- 当Loss丢失事件发生时,用Threshold这个变量表示Loss事件前CongWin的1/2。
- 当指数型增长达到Threshold值时,将指数型增长变为线性增长,进入拥塞避免机制
- 初始时,拥塞窗口设置为1,在前4个时刻,是指数型增长,大小变到8。当CongWin到达Threshold时,将指数型增长变更为线性增长,只增加1。到第8个时刻时,拥塞窗口增大到12。此时,检测到网络有拥塞,在TCP早期版本时,会重新把拥塞窗口将为1,再重新执行上述过程(先指数、再线性)。
- 同时,当网络发送拥塞时,也会更新Threshold得值,把Threshold的值更新为发生拥塞时,拥塞窗口大小的一半,这里也就是Threshold=6。
- TCP早期版本对于发生拥塞时的处理过于激进,直接把大小重新变为1。因此,后续的TCP版本进行了改进,将拥塞窗口更新为发送拥塞时的一半大小,跟Threshold大小一致。因此,更新后直接进入线性增长阶段。
- LOSS事件的处理:
Q:对于LOSS事件为啥处理方式不一样?
A:
- 细想一下,当收到3个重复ACKs,说明这个时候整体网络中还是有传输数据段Segment的能力的(包括传输乱序数据段或是返回的ACK数据),代表可能拥塞的情况相对来说没有那么严重。
- 而当触发timeout超时事件时,可能是发送的数据拥塞到没能到达接收端,或是返回的ACK甚至都没能到达发送端, 说明拥塞的情况相对来说是更严重的,因此需要使用更激进的策略来减少发送速率。
2.3总结
- 当拥塞窗口低于Threshold时,发送端处于慢启动阶段,拥塞窗口呈指数型增加,来探测网络中的拥塞情况。
- 当拥塞窗口超过Threshold时,说明发送速率已经超过了一定阈值,需要降低发送速率。此时,发送端处于拥塞避免阶段,拥塞窗口成线性增长。
- 当发送端收到3个连续的ACK时,说明当前网络可能已经遇到了拥塞,首先将Threshold参数设置为当前拥塞窗口的一半,并将拥塞窗口降低到Threshold值。
- 而当timeout事件发送时,说明网络拥塞的情况可能更严重,此时Threshold参数设置为当前拥塞窗口的一半,并将拥塞窗口直接设置为1个MSS的大小。
- TCP拥塞控制算法:
- 综合示例:
- 最初cwnd=1,ssthresh=16,执行慢开始算法,cwnd大小指数增长
- cwnd到达慢开始门限ssthresh后,转而执行拥塞避免算法,cwnd线性加一增长
- 随着发送速率的增加,若发生超时重传,则将慢开始门限ssthresh减小为当前拥塞窗口的一半、再将拥塞窗口cwnd设置为1,然后执行慢开始-–拥塞避免,重复1、2
- 若发送方收到3个重复确认,则表示分组丢失,发送方立刻重传相应分组;然后执行快恢复算法
- 将慢开始门限ssthresh和拥塞窗口cwnd重新设置为当前拥塞窗口的一半,然后执行拥塞避免算法
- 例题: