TCP协议的快速理解

225 阅读17分钟

TCP协议

本文正在参与 “网络协议必知必会”征文活动

TCP简介

什么是TCP协议?

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 [1] 定义。(引至百度百科baike.baidu.com/item/TCP/33…

TCP的服务

​ 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务。TCP提供的是一种面向连接的、可靠的字节流服务。

​ 面向连接的意思是两个使用TCP协议的应用在彼此传输数据时需要建立一个TCP连接。这一过程就好比是打电话,首先要拨号等待对方接通回应你,此时就建立了一个连接,然后互相可以通话。

​ TCP通过下列方式来提供可靠性:

固定报文段长度:上层传来的数据被分割成TCP认为最适合发送的数据块,并且传递给IP的数据块大小固定,此时的数据块又被称为报文段或段( segment)。

超时重发:当TCP发出一个段后,它启动一个定时器,此时等待目的端确认收到这个报文段。如果发送端未能及时收到接收端的确认,此时发送端会重新发送该报文段。注:在接收端发送确认报文的时候可能有几十毫秒的延迟。

数据校验:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输

过程中的任何变化。如果收到段的检验和有差错, TCP将丢弃这个报文段,此时希望发送端重新发送报文段。

重新排序:由于TCP报文段作为IP数据来传输,而 IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP会将对收到的数据进行重新排序,然后把排好序的数据交给应用层。

丢弃重复数据:IP数据报可能在传输过程中会发生重复,TCP接收端通过标记来确定该数据报是重复数据然后进行丢弃。

流量控制:TCP连接的双方都有一个固定大小的缓冲区,而发送端发送数据的大小不能超过接收端缓冲区的大小,不然会使缓存溢出(一般用滑动窗口实现)。

1.TCP报文段首部格式

​ TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段。一个TCP报文段分为首部和数据两部分,而TCP的全部功能体现在它首部中的各字段的作用。

​ TCP报文段首部的前20个字节是固定的(下图),后面有4n字节(以4字节为单位)是根据需要而增加的选项(n是整数),也就是可变长度。因此TCP首部的最小长度是20字节。

1.png

下面我就简单介绍下各字段含义:

源端口和目的端口 : 各占2个字节,分别写入源端口和目的端口。

序号 :TCP是面向字节流的,在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。(需要序列号的一些原因还是做一些标记,也就是说可以防止接收端接收重复数据)

确认号 : 占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。也就是说如果发送端发送一个报文段序列号是(0-99),此时接收端在确认报文中期望发送端给自己发送的下一个序号是100。

数据偏移 (首部长度) : 占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段占4个bit,因此TCP最多有60字节的首部。(上文提到TCP首部是以4字节为单位,即TCP首部最大为60字节)

六个标志位

U R G :紧急指针(u rgent pointer)字段有效,也就是提高该报文段的优先级,不按之前传送数据方式进行排队。就好比我们在使用(CTRL+C)时,如果不提高优先级,就会排在缓存末尾,这样就中断不了程序。

A C K :确认序号有效,仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。。

P S H :接收方应该尽快将这个报文段交给应用层,意思就是如果发送端PSH置位1时,立即创建一个报文发送出去,接收方接收到该报文就会立即交给应用层。

R S T :重建连接。

S Y N :同步序号用来发起一个连接,当SYN为1时,说明这是一个连接请求或连接接收报文。

F I N :发端完成发送任务,当FIN=1时表明此段报文段的发送方数据已经发送完毕。

窗口 : 窗口大小是一个 16 bit字段,因而窗口大小最大为 6 5 5 3 5字节。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。

检验和 : 占2字节。检验和字段检验的范围包括首部和数据这两部分。和UDP用户数据报一样,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式和UDP用户数据报的伪首部一样。但应把伪首部第4个字段中的17改为6(TCP的协议号是6);把第5字段中的UDP中的长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。

紧急指针 : 占2字节。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时也可以发送紧急数据。

选项:最常见的可选字段是最长报文大小,又称为 MSS (Maximum Segment Size)。每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN标志的那个段)中指明这个选项。它指明本端所能接收的最大长度的报文段(最大长度的报文段+TCP首部=整个TCP数据报)。

2.TCP可靠传输的实现

2.1.滑动窗口

​ TCP的滑动窗口是以字节为单位的。下面我们来谈谈滑动窗口的工作原理,这里我们假设A给B发送数据,只在一个方向上传输,而且字节编号也取的很小。假设A接收到了B发来的确认报文段,其中窗口大小(rwnd)是20字节,确认号为31。(此图摘自计算机网络(谢希仁))

2.png

​ 发送窗口的位置由后沿和前沿共同决定,而其中后沿不可能向后移,因为前面的以确认数据在缓存中已经丢弃了,所有后沿只可能是不动(等待接收端的确认)或者往前移。而前沿一般都是往前移动,也有可能保持不动(可能是没有收到确认,也有可能确认数据包中的窗口变小了,此时得缩小窗口)。

​ 由于此时确认报文中给定的确认号是31,所以31前面的序列号已经收到确认,也就是说此时A需要从31的序列号开始发送数据,在此过程中发送窗口的状态应该如何标记或者维护呢?

3.png

从上图中可看出,用P1,P2,P3三个指针来指向相应的字节序号,P1表示确认序列号(P1前的数据都已发送并确认完毕),而大于P3的是不允许发送的,此时就有了:

P3-P1=A发送的窗口大小

P2-P1=已发送的但未收到确认的字节数

P3-P2=允许发送的但未发送的字节数

​ 在B接收窗口中可以看出序列号为31并未收到,而32-33这两个序号却到达了,此时31可能是丢失或者还滞留在网络中,然而B只能发出确认号为31的确认报文段,当接收方A接收到该重复确认报文后,说明数据31序号的数据没有按序到达,由于这是针对31号数据的第一个重复确认,因此不会引起快重传(下文会提到快重传)。

​ 假设接收方收到了31号数据,将其存入接收缓存里然后交付给应用层,此时发送一个31-33的确认报文段,假设发送窗口大小还是20,那么确认报文段汇中的rwnd=20,ack=34,在A的发送窗口接收到了确认报文后,P1和P3向前滑动3个单位此时窗口序列为3-53。此时接收方B向前移动3个序号,接收窗口仍保持不变,里面序号为34-53。

5.png

​ 如果此时发送方A发送窗口的数据已经全部发送完毕(如上图),A此时停止发送,如果此时A一直未收到接收方的确认报文段,此时的数据可能停滞在网络中或已丢失。为了保证可靠传输,A重新发送这部分数据(超时计时器控制)。如果A收到了确认数据后,窗口向前滑动。

注:

发送方的窗口并不总是和接收方的窗口一样大(发送方的发送窗口由接收方的接收窗口设置的)。

对于没有按序到达的数据如何处理,TCP并没有明确规定,只不过TCP通常对于不按序到达的数据先临时存放在接收窗口中,等待该字节收到后,才交付给上层处理。

3.TCP的流量控制

3.1.利用滑动窗口实现

​ 简单来说流量控制就是让数据传输端传输的速率不要太快,让数据接收端来得及接收,利用滑动窗口机制可以很方便的在TCP连接上实现对数据传输端的流量控制。

6.png

​ TCP的流量控制就是合理设置了滑动窗口的大小,如上图所示,假设这里已经建立了TCP连接,并且接收方和发送方的窗口大小都为400。在发送端发送seq=201时,此时数据丢失,然后接收端就给一个确认报文段,把窗口设置为300(对发送端进行流控),ACK=1(上文提到的确认号),ack=201(表示对序列号为201之前的字节数据进行累计确认,期望接收序列号为201)。后续的发送过程于此类似,最后把窗口大小调整为0,表示停止发送据,等接收方有了多于的存储缓存空间,然后接收端向发送端发送一个rwnd>0的报文段,此时接收方就会等待发送方发送数据。

注:TCP的窗口单位是字节,不是报文段(上文有所解释什么是报文段)。

3.2.TCP传输速率

​ 应用进程把数据推到TCP的发送缓存后,该进程就不管数据是怎么传输的了,剩下的发送任务就交由TCP来控制了.可以用不同的机制来控制TCP报文的发送时机。第一种机制是TCP维持一个变量,它等于最大报文长(MSS),也就是说只要缓存中存在数据达到了MSS,TCP就会把该段数据组装成报文段发送出去。第二种机制是由发送方指定要求发送报文段。第三种就是发送方的一个计时器到了,这个时候就把当前缓存的数据组装成一个报文段(不能超过MSS)发送出去。

​ 如何控制TCP发送报文的时机至今都是一个较为复制的问题。

为什么要控制报文段发送时机呢?

假设只发送1个字节的数据,然后传输层TCP以及网络层IP都会分别给该数据加上首部,此时发送的数据包至少41字节。在接受方确认发送时,又会构成一个至少40字节的报文段,这种传输效率是非常低的,因此应该适当推迟发送确认报文,让接受方等待一段时间,也就是说在接收方设置一个计时器或者接受缓存到达一定大小时才发送确认报文。

4.TCP的拥塞控制

​ 对于拥塞控制流量控制,想必也有一些人可能还分不清拥塞控制和流量控制。拥塞控制和流量控制虽然采取的动作很相似,但拥塞控制与网络的拥堵情况相关联,而流量控制与接收方的缓存状态相关联。也就是说网络中某一资源的需求超过了该资源所能提供的可用部分

为什么需要拥塞控制?

​ 我们知道,两台主机在传输数据包的时候,如果发送方迟迟没有收到接收方反馈的ACK确认,那么发送方就会认为它发送的数据包丢失了,进而会重新传输这个丢失的数据包。然而实际情况有可能此时有太多主机正在使用信道资源,导致网络拥塞了,而A发送的数据包被堵在了半路,迟迟没有到达B。这个时候A误认为是发生了丢包情况,会重新传输这个数据包。这样不仅会浪费资源,反而让网络更加拥塞。

7.png

​ 在上图中,横坐标是提高的输入负载(网络负载)也就要是单位时间内输入的单位分组数目。纵坐标是吞吐量,代表单位时间内从网络输出的分组数目。

​ 在理想拥塞控制状态下可以看出在吞吐量饱和之前,吞吐量等于输入负载呈45°的斜线,但是当负载超过一定限度时,吞吐量不再增加,即达到了饱和状态。

​ 在实际的拥塞控制下,随着负载的增大,网络吞吐量速率会慢慢的减小。也就是说在还未到达饱和时,就有一部分输入分组丢失了。

​ 在无拥塞控制下,当负载增加到某一数值时,吞吐量反而下降直至为0,这样网络就无法工作,称为死锁

TCP的四种拥塞控制算法(慢开始,拥塞避免,快重传,快恢复)

为了便于理解假定: 1.数据是单方向传送,而另一个方向只传送确认 2.接收方总是有足够大的缓存空间,因而发送方发送窗口的大小由网络的拥塞程度来决定 3.以TCP报文段的个数为讨论问题的单位,而不是以字节为单位

慢开始和拥塞避免:该方法是基于窗口的拥塞控制。该算法的思路是当主机开始发送数据时,由于并不清楚网络的负载情况,所以如果把大量数据注入到网络中,那么就极大可能发生网络拥塞,所以应该从小到达慢慢的增大发送窗口。而在一步一步增加发送窗口时我们是以指数形式进行增加,次阶段称为慢启动 。当增加到某一个值时,称为阈值(ssthresh),此时就不再进行指数形式增加,而是呈线性方式进行增加,此阶段就称为了拥塞避免。(如下图)

这里的慢开始是指一开始向网络注入的报文段少,而不是cwnd增长速度慢,而拥塞避免并不是完全能够避免拥塞,而是使网络不容易出现拥塞。

当重传计时器超时的时候,就可能发生了拥塞,然后进行以下工作:

  • 把阈值(ssthresh)更新为发生拥塞时cwnd值的一半;
  • 把cwnd值减至为1,重新进行慢启动算法。

8.png

判断出现网络拥塞的依据是:没有按时收到确认报文(即会发生重传)。

​ 有时,个别报文段在网络中丢失,并不是因为网络拥塞而没有到达,此时重传计时器超时,就会误以为网络发生拥塞,然后就会出现如上文所述的操作,此时cwnd=1会严重降低了传输效率。采用快重传算法可以让发送方尽早的知道有个别报文段的丢失。

什么又是快重传呢?

所谓快重传就是不等待重传计时器超时就已经重传了。

​ 要求接收方不要等待自己发送数据时才进行捎带确认,而是立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段进行确认;

​ 如下图所示,发送方在发送M1和M2报文段都收到了确认,当接收方没有收到M3报文段却收到了M4,此时就发现M4报文段不是按序到达的报文段,说明此时M3报文段丢失,由于快重传算法的原因,接收方必须立即发出M2重复确认。快重传算法规定,当重复确认到达3次时,说明接受方没有收到M3报文段,此时应该立即对M3报文段进行重传。这样发送方就不会出现超时重传,也就不会误以为发生了网络拥塞。使用快重传可以使整个网络的吞吐量提高20%。

9.png

当发送方知道现在只是丢失了个别报文段,于是启动**快恢复算法**,而不是慢启动算法。

什么又是快恢复算法呢?

发送方一旦收到了3个重复确认就会知道个别报文段丢失了,然后不启动慢开始算法,而是启动快恢复算法

​ 要求调整门限值(ssthresh)为拥塞窗口的一半(ssthresh=cwnd/2),同时设置拥塞窗口为cwnd=sshresh,并执行拥塞避免算法。

​ 也有些是把cwnd的值调为ssthresh+3xMSS(最长报文大小),原因是因为发送方接收到了3个重复确认分组,说明这三个分组离开了网络,不再消耗网络的资源,而是停留在接收方的缓存中,所以可以适当把拥塞窗口扩大一些。

如下图中就包含了4大拥塞控制算法,其中在输出轮次达到22时就进行了快恢复算法,也就是说当发生快重传时,后续启动的不是慢开始算法而是拥塞避免算法。

10.png