浅谈TCP协议

TCP的特点

  • TCP是面向连接的传输层协议。
  • TCP是点对点的,每一条TCP连接只能有两个端点。
  • TCP提供可靠交付的服务。
  • TCP提供全双工通信。
  • TCP面向字节流。

TCP报文段的首部格式

在这里插入图片描述
TCP报文首部与其功能息息相关,所以想深入了解TCP功能可以先看看TCP首部。如果有看不懂的地方,在熟悉了TCP功能之后再回顾一遍就会一目了然。
本文简单概况各个字段的作用,如果想了解更细节的可以查阅其他书籍或博客。

  • 源端口
    2个字节,记录发送方端口。

  • 目的端口
    2个字节,记录接收方端口。

  • 序号
    4个字节,记录本报文的第一个字节的序号,这样就可辨别报文的顺序。

  • 确认号
    4个字节,是期待收到对方下一个报文段第一个字节的序号。

  • 数据偏移
    4个比特,记录了本报文中数据部分的起始位置。(其实也就是记录了首部的长度,可以注意到首部某些字段长度可变,所以需要记录长度。)

  • 保留
    6个比特,保留为今后使用,目前全为0。

  • 紧急URG标志位
    表示当前报文特别紧急,需要优先处理。(标志位都占1个比特)

  • 确认ACK标志位
    表示是否收到。(区别于确认号,确认号是ack,而确认标志位是ACK,确认号记录的是收到的序号,而确认标志位表示的是是否收到,只有0和1两种可能。)

  • 推送PSH标志位
    很少使用。当两个应用进行交互式通信时,有时在一端的应用程序希望在键入一个命令后立即能够收到对方的响应。这种情况就将该标志位置1。

  • 复位RST标志位
    当RST=1,表示TCP连接中出现严重差错,必须释放连接,然后重新建立连接。

  • 同步SYN 标志位
    在建立TCP连接时用来同步序号。
    当SYN=1,ACK=0,表示这是一个连接请求报文。
    当SYN=1,ACK=1,表示这是一个同意连接的响应报文。

  • 终止FIN标志位
    用于释放一个连接。当FIN=1,表示数据发送完毕,请求释放连接。

  • 窗口
    2个字节,与滑动窗口功能相关,用于记录发送本报文的一端接收窗口的大小。窗口值作为接收方设置发送窗口值大小的依据。

  • 检验和
    2个字节,检验首部和数据部分的正确性。在计算检验和时,要在TCP报文段的前面加上12字节的伪首部,格式与UDP相同。(可以搜索学习一下伪首部)。

  • 紧急指针
    两个字节。在紧急URG标志位为1时有意义,记录了紧急数据末尾所在的位置。值得注意的是:窗口为0也可以发紧急数据。

  • 选项
    长度可变,最长40字节。(选项前面都是不可变的,一共20字节。)
    选项有很多,比如最长报文段长度MSS,窗口扩大,时间戳等。

  • 填充
    长度可变。TCP首部长度必须是4字节的倍数,使用使用填充来凑长度。

TCP可靠传输的工作原理

TCP协议保证数据传输可靠性的方式主要有:

  • 校验和
  • 序列号
  • 确认应答
  • 超时重传
  • 流量控制
  • 拥塞控制
  • 连接管理

校验和

计算方式:在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。
发送方:在发送数据之前计算检验和,并进行校验和的填充。
接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。

在这里插入图片描述

注意:如果接收方比对校验和与发送方不一致,那么数据一定传输有误。但是如果接收方比对校验和与发送方一致,数据不一定传输成功

确认应答与序列号

在这里插入图片描述
TCP对要发送的所有数据的字节进行编号。
应答的流程:

  1. 客户端发送数据中的1~1000个字节
  2. 服务器发送确认号ack=1001的报文,表示正确收到了前1000个字节,接下来请发送第1001位。
  3. 如果接收出错,(比如收到了1003,但没收到1002)会发送确认号为出错的编号。(失序不会影响接收到的数据,接收方会存储并排序好。)

超时重传

发送方报文发送后迟迟未收到接收方的相关的ack,可能是因为某种原因报文未到达,当超过了某个设定的时间,就会触发重传。这就是所谓的超时重传。

超时重传的概念非常简单,但复杂的问题其实是超时时间的选择。重传时间太小会造成很多不必要的重传,增加网络负荷,太长又会使网络空闲,降低传输效率。

TCP采用了一种自适应算法。

计算RTT(报文段往返时间)的加权往返平均值RTTs。
新RTTs = ( 1 - α ) * ( 旧的RTTs ) + α * (新RTT)

RTTd是RTT的偏差的加权平均值。
新RTTd=(1-β) * (旧的RTTd)+β * |RTTs-新RTT|

超时重传时间RTO=RTTs+4*RTTd

所以超时重传时间便是RTO。

流量控制

所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制。

滑动窗口

在这里插入图片描述

我们以单工连接为例子,发送方有一个发送窗口,接收方有一个接收窗口。
事先,接收方会告知发送方窗口大小,即告诉对方自己能承受的窗口大小。如果窗口有变化,之后会再通知。

我们可以用p1,p2,p3三个指针表示发送窗口。
p1以前的表示已经发送但并收到确认的字节。
p1-p3表示窗口以内。窗口内的数据都是当前允许发送的。
p1-p2是已经发送过了,但还为收到确认,为了防止发送失败(如超时重传)暂时存在窗口内。
p2-p3是允许发送但还没发送的字节。
p3以后是当前不允许发送的字节。

当某个字节被发送p2指针会向前移动一格。
当某个字节发送并收到确认,p1和p3向前移动一格,一般来说,窗口大小是固定的。
窗口可能向后收缩,即p1不懂,p3向后移动,这发生在对方通知窗口缩小了。但TCP标准不赞成这样做,因为这样可能发送方在收到这个通知以前语句发送了窗口中的许多数据,现在又收缩窗口,不然发送,就会产生错误。

接收方在接收到正确顺序的字节会发送确认报文,并将窗口向前移动一格。
收到顺序不正确的字节则会暂时存储,等待正确报文到达,然后一并发送确认并移动窗口。

滑动窗口和TCP首部的一些联系(自己的理解)

在这里插入图片描述
当我们熟悉了滑动窗口后再看TCP报文首部,就能将一些字段联系起来。
主要是这些字段:序号,确认号,窗口。
在发送报文中,序号会是数据部分第一个字节的序号,这样就可以让对方知道报文顺序。
在接收报文中,确认号是期待收到对方下一个报文段的第一个数据字节的序号。窗口值协商了接下来发送窗口的大小。
通过这样的方式,就完成了滑动窗口的信息交换。

滑动窗口存在的糊涂窗口综合征

糊涂窗口综合征有时会使TCP的性能变坏。设想一种情况:TCP接收方的缓存已经满了,而交互式引用进程一次只从接收缓存中读取一个字节(接着就使接收缓存空间仅腾出1个字节),然后向发送方发送确认,并把窗口设置为1个字节(但发送的数据报是40字节)。接着,发送方又发来了一个字节的数据(请注意,发送方发送的IP数据报是41字节长)。接收方发回确认,仍然将黄口设置为1个字节,这样网络效率很低。

要解决这个问题可以让接收方等到接收缓存有足够的空间。
比如:

  • 能够接收一个最大报文,或者缓冲区的一半,再来发送窗口通告,这样就不会产生小报文
  • 如果一个窗口低于一半的Window Size,或者不够一个MSS,就直接通告窗口为0

滑动窗口的死锁问题

这里面涉及到一种情况,如果B已经告诉A自己的缓冲区已满,于是A停止发送数据;等待一段时间后,B的缓冲区出现了富余,于是给A发送报文告诉A我的rwnd大小为400,但是这个报文不幸丢失了,于是就出现A等待B的通知||B等待A发送数据的死锁状态。为了处理这种问题,TCP引入了持续计时器(Persistence timer),当A收到对方的零窗口通知时,就启用该计时器,时间到则发送一个1字节的探测报文,对方会在此时回应自身的接收窗口大小,如果结果仍未0,则重设持续计时器,继续等待。

拥塞控制

在计算机网络中的链路容量(即带宽)、交换节点中的缓存和处理机等,都是网络的资源。在某段时间,若堆网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就会变差。这就是所谓拥塞。(供不应求)

所谓拥塞控制就是防止过多的数据诸如到网络中,这样可用使网络中的路由器或链路不致过载。
流量控制往往是点对点通信量的控制,是端到端的问题,而拥塞控制是全局性的。

TCP的拥塞控制方法

TCP的拥塞控制是基于窗口的,发送方让自己的发送窗口(滑动窗口的发送窗口)等于拥塞窗口
TCP进行拥塞控制的算法有4中:

  • 慢开始
  • 拥塞避免
  • 快重传
  • 快恢复
    在这里插入图片描述

慢开始

慢开始的思路:当主机开始发送数据时,并不清楚网络的符合状况,所以由小到大主键增大发送窗口(由小到大逐渐增大拥塞窗口数值)。直到增加到ssthresh慢开始门限,改为使用拥塞避免算法。

慢开始有三个关键点:初始拥塞窗口值,拥塞窗口cwnd的增加量,慢开始门限ssthresh

初始拥塞窗口值:
SMSS是发送方的最大报文段。
若2190 <SMSS字节,则设置初始拥塞窗口cwnd=2 * SMSS字节,且不得超过2个报文段。
若1095 < SMSS < 2190字节,则设置初始拥塞窗口cwnd=3 * SMSS字节,且不得超过3个报文段。
若SMSS<=1095字节,则设置初始拥塞窗口cwnd=4 * SMSS字节,且不得超过4个报文段。

拥塞窗口cwnd每次的增加量:
拥塞窗口cwnd每次的增加量=min(N,SMSS)
N是原先未被确认,但现在被刚收到的确认报文段所确认的字节数。
当一个轮次过后,拥塞窗口会翻倍。

慢开始门限ssthresh:
一开始会有一个慢开始门限(书上默认是16,但我不确定这个是不是正确的)。当慢开始的拥塞窗口>=ssthresh则会使用拥塞避免算法。拥塞避免会把拥塞窗口值+1(看上面的图),直到网络发生了拥塞,慢开始门限则会被修改ssthresh=swnd/2,同时将拥塞窗口cwnd=1。

拥塞避免

其实上面讲的超不多了。
当慢开始的拥塞窗口>=ssthresh则会使用拥塞避免算法。拥塞避免会把拥塞窗口值+1(看上面的图),直到网络发生了拥塞,慢开始门限则会被修改ssthresh=swnd/2,同时将拥塞窗口cwnd=1。

快重传

有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动了慢开始,又将拥塞窗口设置为1,因而降低了传输效率。
快重传能解决这个问题。当接收方收到了失序的报文就立即发送快重传报文(对正确顺序的最后一个报文的确认报文),接收方每收到一个失序报文就发送快重传报文。接收方一连接收到3个重复确认,就知道对方没收到哪一个报文,接下来:

  1. 立即进行重传。
  2. 然后把ssthresh设置为cwnd的一半。
  3. 把cwnd再设置为ssthresh的值(具体实现有些为ssthresh+3)。
  4. 重新进入拥塞避免阶段。

快恢复

后来的“快速恢复”算法是在上述的“快速重传”算法后添加的,当收到3个重复ACK时,TCP最后进入的不是拥塞避免阶段,而是进入快恢复。

  1. 当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。
  2. 再收到重复的ACK时,拥塞窗口增加1。
  3. 当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。

连接管理

TCP的连接可以分为三个阶段:

  1. 连接建立
  2. 数据传输
  3. 连接释放
    数据的传输(确认应答与序列号)其实前面已经讲了,还有其他的细节也在上面讲过了。
    接下来看连接建立释放,以及TCP的11中状态。

TCP的连接建立(三握手)

在这里插入图片描述
上图已经很详细了。
说一下需要注意的地方,主要是和TCP首部的一些联系。
SYN=1,ACK=0表示请求连接。SYN=1,ACK=1表示同意连接。(之前讲过)
三个报文都不能携带数据,只能有首部。
ACK和ack不一样,一个是标志位,一个是确认号。(可以翻回TCP首部再看一下)
seq是序号,ack是确认,所以ack的值必然是上一条报文的seq+1。
其实我也有一个比较迷惑的点就是x和y值的选择,似乎是随意的。

为什么需要三握手,不可以两握手?

假设出现一种异常情况,即A发出的第一个连接请求报文并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才达到B。本来这是一个早已经失效的报文,但B收到此失效的连接请求报文段后,就误以为是A又发出一次新的连接请求。于是就向A发送确认报文段,同意建立连接。假设不采用三握手,那么B发出确认,新连接就建立了,但实际上出错了。

TCP的连接释放(四挥手)

在这里插入图片描述
上图很详细,基本上的东西也和三握手类似。

半关闭状态

客户端发FIN,服务端回ACK后,(也就是前两个报文发完后)TCP连接处于半关闭状态,即客户端已经没有数据要发送了,但服务器若发送数据,客户端仍要接收。也就是说从服务器到客户端这个方向的连接并未关闭,这个状态可能持续一段时间。

为什么需要TIME_WAIT状态

停留在time_wait会停留2msl,msl就是最长分节生命期,是任何IP能够在英特网中存活的最长时间。(2msl一般是1分钟到4分钟之间)

每个数据报有一个跳限的8位字段,它的最大值是255。尽管这个跳限不是真正的时间限制,我们仍然假设:具有最大跳限的分组在网络中存在的时间不可能超过msl时间。

分组在网络中“迷途”通常是路由异常的结果。某个路由器崩溃或两个路由器之间的某个链路断开时,路由协议需要花数秒甚至数分钟的时间找到另一条通路(也就是收敛,这里应该说的是外部网关协议)。因为某个分组“迷途”了之后,超时重传了第二个分组,第二个分组通过某条路径到达目的地。然而不久后,之前崩溃的链路恢复了,“迷途”的分组最终也到达了目的地,这样就有两个重复的分组到达了目的地。
TIME_WAIT保证了本连接的分组消亡,不会进入下一个连接。可靠地实现了连接的释放。

TCP的有限状态机(TCP的11中状态)

在这里插入图片描述

TCP的11个状态图需要配合三握手和四挥手图一起看。之所以放到最后是因为状态机贯穿整个TCP连接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章