可靠传输—-TCP

  • Post author:
  • Post category:其他





一、TCP的特点

1.是面向连接的运输层协议,通信前需要建立连接(进行一些通信前的准备工作),通信结束后要释放连接。

2.TCP提供可靠的交付服务,实现了无差错,不丢失,不重复,并且按序到达。

3.TCP提供全双工通信。应用进程可以随时将信息交给TCP,TCP会将其存储在缓存中,并在合适的时候发送出去。同时TCP也可以接收数据并存入缓存,上层的应用进程会在合适的时候读取缓存中的数据。

4.面向字节流。TCP中的“流”指流入进程和流出进程的字节序列。虽然应用进程是一次向TCP传递一个数据块,但是TCP并不会区分多个数据块,而是将从同一个进程传递下来的所有数据看做是一个数据流,存储在缓存中。同样的接收数据时,TCP只负责将收到的字节流传给对应的进程,而不区分数据在源主机的进程中传下来有几个数据块以及数据块的划分。所以应用进程需要有从字节流中接收信息的功能。



二、TCP报文格式

TCP报文

源端口(2字节):发送主机上程序的端口

目的端口(2字节):目标主机上的目标程序的端口号

序号(4字节):[0,2^32-1],在一个TCP连接传输中的字节流,每一个字节都标上了序号。序号表示当前报文携带数据的首个字节的序号

确认号(4字节):是期望收到对方下一个报文段的第一个数据字节的序号。例如确认号为501,就表示501之前的序号都已经正确收到

首部长度(4位):因为首部的长度不确定,需要区分首部和数据部分

保留(6位):保留为今后使用,但目前都设置为0

紧急URG(1位):URG=1,表示这是一个紧急报文,数据中有紧急数据。发送方接收到应用进程发送来的紧急数据后就会将其插入到报文段的首部。需要与后面的紧急指针配合使用

确认ACK(1位):仅当ACK=1时确认号字段有效。建立连接后,所有传输的报文ACK=1

推送PSH(1位):当应用进程希望某个数据立刻传输并得到回应,TCP就会将PSH设置为1,这样就会立即创建一个报文段发送出去,同样,接收方接收到PSH=1的报文也会尽快的向上交付,而不会等到整个缓存都满了再交付(很少使用)

复位RST(1位):RST=1,表示TCP连接出现严重差错(如主机崩溃等),必须释放连接。RST=1还用来拒绝一个非法的报文段或拒绝一个连接

同步SYN(1位):在建立连接时用来同步序号。SYN=1就表示这是一个连接请求报文或连接接收报文

终止FIN(1位):用来释放连接。FIN=1表示这是一个请求连接断开报文

窗口大小(2字节):窗口指发送本报文段的一方的接收窗口大小,表示从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。

校验和(2字节):校验包括首部和数据两部分。在计算校验和时要在TCP报文段的前面加上12字节的伪首部(源IP地址,目的IP地址,0,6,TCP长度),接收方收到报文段也要加上伪首部来计算校验和,来确认数据是否有差错。

紧急指针(2字节):表示紧急数据的长度,仅当URG=1时有效。当所有紧急数据处理完之后TCP就会告诉应用程序恢复到正常状态。(当窗口为0时也可发送紧急数据)


选项

:最长40字节。

  1. 最大报文长度MSS:指每一个TCP报文段中数据字段(TCP报文段长度-TCP首部长度)的最大长度。默认为536字节。
  2. 窗口扩大选项(3字节):TCP首部中窗口字段长度为16位,对于包含卫星信道的网络可能不够大,需要扩大窗口。其中有一个字节表示移位值S(最大为14),新的窗口值为2^(16+S),窗口增大可以在双方建立连接时进行协商,若不再需要扩大窗口,可发送S=0的选项,使窗口大小回到16。
  3. 时间戳选项(10字节):主要的字段是时间戳值字段(4字节)和时间戳回送回答字段(4字节)。时间戳选项有2个功能,一是用于计算往返时间RTT。发送方在发送报文段时把当前时间时钟的时间值放入时间戳字段,接收方在确认报文中将时间戳字段值复制到时间戳回送字段。以此便可算出RTT。二是用于处理TCP序号超过2^32的情况,又称防止序号绕回PAWS。当用高速网络时序号很可能被重复使用,若用2.5Gbit/s的速度发送报文,则不到14秒序号就会重复,加上时间戳就可以区分重复的序号
  4. 选择确认选项:若收到的报文段无差错但是不是按序到达,选择确认选项可以提醒发送方只发送未收到的报文段。(很少用到)





三、实现可靠传输



1.停止等待协议

“停止等待”就是每发送一个分组就停止发送,等待对方的确认。在收到确认后再发送下一个分组。

在这里插入图片描述

可能出现的差错

  • M1在传输过程中丢失
  • B对M1的确认信息在传输过程中丢失
  • B接收到有差错的M1,直接丢弃,但没有发送错误信息给A


所以为了解决这些可能出现的差错,就需要超时重传。超时重传指A只要超过一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,因而重传前面发送过的分组。为了实现超时重传,必须要有一个计时器以及超时重传的时间。

  • 先介绍一下关于超时计时器的工作原理以及完成超时重传的一些准备最后再介绍如何获取重传时间。


每发送一个分组就会设置一个超时计时器,如果在超时计时器到期之前收到了对方的确认就撤销计时器并且不会进行重传。否则就会进行重传。


重传的准备主要包括2方面:

  1. A发送完一个分组,必须暂时保存发送的分组的副本,用于可能发生的重传。只有在收到相应的确认后才能删除暂时保存的分组副本。
  2. 分组和确认分组必须进行编号,用于确认哪一个发送的分组收到了确认,而哪一个分组还没有收到确认。(在后面连续ARQ协议中确认号和序号可以实现这个功能)


重传时间的选择


  • 超时重传设置的时间应当比数据在分组传输的平均传输往返时间更长一点。如果设置的太短会浪费通信资源,太长会使通信的效率很低。

  • TCP采用了自适应算法,得到超时重传时间RTO。记录一个报文发送的时间和确认报文到达的时间,两者相减就得到了报文段的往返时间RTT。但是为了更加平滑的标识往返时间,TCP保留的是加权平均往返时间RTTs,每当第一次测量到RTT时,RTTs就取RTT值,之后没测量到一次RTT值,就更新RTTs,新的RTTs=(1-α)*(旧的RTTs)+α*新的RTT,α一般取0.125。超时重传时间RTO应该比RTT长一点,RTO=RTTs+4*RTTD。RTTD是RTT偏差的加权平均值,第一次测量时取RTT的一半,之后取新的RTTD=(1-β)*(旧的RTTD)+β*|RTTs-新的RTT|,β一般取0.25。

  • 但是还可能有一种情况,当重传后,由于确认报文是相同的,就无法判断收到的确认报文是第一次传输的确认报文还是重传的确认报文,不能判断的话就会影响RTTs时间的准确性。所以在计算加权平均RTTs时,只要报文重传了就不采用其往返时间的样本,但是每重传一次,RTO都会增加一些,一般取新的RTO=2*旧的RTO,为得是避免报文段的时延突然增大,超时重传时间却没有增加。



2.连续ARQ协议和滑动窗口协议

  • 因为等待确认再重传的传输效率太低,连续ARQ协议就是改善了这一个缺点。
  • 先介绍一下发送窗口,位于发送窗口内的报文可以连续发送出去,而不需要等待对方的确认。同时发送方每接收到一个确认,就把发送窗口向后移一位。接收方一般采用累积确认,即接收方在接收到报文后,不是立刻回复确认信息,而是在收到几个报文后,对按序到达的最后一个报文发送确认,这就表示这个报文之前的所有报文都收到了。但是确认推迟的时间不应超过0.5秒,若收到一连串具有最大长度的报文段,则必须每隔一个报文段就发送一次确认。


    在这里插入图片描述
  • 累积确认的优点是容易实现,即使确认丢失也不必重传,因为后续报文到达了会发送确认报文,且这个确认报文包含了对之前所有报文段的确认。但缺点是,不能正确的向发送方反映出接收方已经正确收到的所以分组的信息。比如发送方发送了前5个分组,而中间的第3个分组丢失了,这时候接收方只能对前2个发出确认。这样发送方就不知道后面3个分组的下落,因此只能把后面的3个分组都重传一次,这种机制叫Go-back-N(回退N),表示需要再退回来重传已发送过的N个分组。
  • 再来介绍一下滑动窗口协议
  • 滑动窗口是以字节为单位的,发送方的窗口称为发送窗口,接收方的窗口称为接收窗口,但由于TCP是全双工通信,其实通信双方都有发送窗口和接收窗口,但为了方便起见我们假设数据传输只在一个方向进行,即A发送数据,有发送窗口,B接收数据,有接收窗口。窗口的大小会在TCP连接时由双方沟通确认,同时在通信过程中由于网络堵塞、主机的处理速度等的不同在动态改变(改变由报文中的窗口值得知)。


    在这里插入图片描述
  • 发送窗口:在没有收到B的确认的情况下,A可以将发送窗口内的数据都发送出去,凡是已经发送过的数据,在未接收到确认信息时会存储在缓存中,以便超时重传时使用。发送窗口的大小主要由接收方的接收窗口以及网络堵塞情况决定。(缓存下面会介绍)发送窗口主要有2部分,一部分是已发送但未收到确认,一部分是允许发送但尚未发送,称为可用窗口。发送窗口之前是已发送并已收到确认的数据,这些数据不会被保存,发送窗口之后是还不允许发送的数据,也是暂时存储在缓存中。当接收到确认信息时且且接收方的窗口不变时,发送窗口会向前移动,但是大小不变。当收到接收方的窗口扩大的信息时,发送窗口会变大,也就是前沿会向前移动;当收到接收方的窗口减小的信息时,由于TCP的标准强烈不赞成发送窗口的前沿收缩,所以发送窗口会在接收到确认信息时不向前移动来实现发送窗口的缩小。
  • 接收窗口:在接收窗口以前是已经收到的且发送过确认的数据,接收窗口中是允许接收的数据,接收窗口之后是不允许接收的数据。接收窗口的大小有接收方的接收缓存和接收程序的接收速度决定。接收数据时,如果数据监测出差错就直接丢弃,如果没有差错就接收,接收也分为2种情况,当数据按序到达时,即假设31,32,33是按顺序到达的,那么B就会按照累积确认的原则发送确认信息,如果B在此时发送确认信息,其中的确认号为34(即期望收到的数据),并且接收窗口会向后移动相应的位数。如果数据不是按序到达的,B会先接收下来,但是还是只能对按序到达的数据的最高序号给出确认,如图中,如果32,33先到,31未到(可能丢失,也可能滞留在网络中某处),那么B发送的确认报文段中的确认号依旧为31。(当数据未按序到达时,如果想减少重传的数据,可以使用可选选项中的确认SACK选项,但是很少使用)。
  • 发送缓存:发送缓存包括2部分,已发送但未收到却信息的数据,未发送的数据(包括发送窗口中的未发送数据、发送窗口之后的数据。发送窗口通常只是发送缓存的一小部分。发送应用程序传给TCP的数据就是先存在发送缓存中,所以发送应用程序必须控制写入缓存的速率,不能太快,否则发送缓存会没有空间。
  • 接收缓存:接收缓存也包括两部分,按序到达但未被应用程序接收的数据,未按序到达的数据。如果接收应用程序来不及读取收到的数据,接收缓存最终会被填满,使接收窗口减少为0,反之,如果接收程序可以及时接收数据,接收窗口就可以增大,但是最大不能超过缓存的大小。



3.流量控制



利用滑动窗口实现流量控制

流量控制就是控制发送方数据的发送速度,使得接收方来的及接收。主要就是通过接收方告知发送方自己的接收窗口大小,然后发送方以此控制发送窗口的大小,发送方的发送窗口大小不能超过接收方的接收窗口大小,这个控制过程是贯穿整个TCP连接的。


  • 死锁情况

    :A是发送方,B是接收方,当B向A发送了零窗口的报文后,过了一段时间,B的缓存又有了空间,于是B向A发送了rwnd=400(窗口大小为400)的报文,但是这个报文在传输过程中丢失了,那么之后A就会一直等待B发送的非零窗口报文,B会一直等待A发送的数据,这样就形成了一个死锁局面。为了解决这个问题,TCP为每一个连接设有一个

    连接计时器

    ,只要发送方接收到零窗口通知,就启动连接计时器,若到了设置的时间就发送一个零窗口探测报文,接收方如果还是零窗口就重置连接计时器。如果不是零,就按正常的规则改变窗口大小,发送数据。(TCP规定,即使设置为零窗口,也必须接收零窗口探测报文段、确认报文段、携带紧急数据的报文段)



传输效率

应用进程将数据传输到TCP的发送缓存之后,就由TCP来控制数据的发送了,有三种机制来控制TCP发送的时机。

  • 第一种是当缓存中存放的数据达到MSS字节时,就组装成一个TCP报文段发送出去。
  • 第二种是由发送方的应用进程指明要求发送报文段,即push操作。
  • 第三种是发送方的一个计时器期限到了,这时就会把当前已有的缓存数据装入报文段发送出去(但长度不能超过MSS)。(如连接计时器,超时计时器等)。



4.堵塞控制

当网络中对某一资源(指带宽、交换节点中的缓存和处理机制)的需求超过了该资源所能提供的所能提供的可用部分,就会导致网络性能下降,这就是堵塞现象。堵塞控制是一个很复杂的问题,而不仅仅只是简单的扩充某个堵塞处的容量或提高其性能,因为网络是一个庞大的互相协调的整体,只解决一处是毫无意义的,所以就需要一个机制来防止过多的数据注入网络,使得网络中的链路或路由器不至于过载,这就是堵塞控制。

  • TCP进行堵塞控制的算法有四种,包括慢开始、堵塞避免、快重传和快恢复,堵塞控制也是基于窗口的堵塞控制。
  • TCP会维护一个堵塞窗口cwnd,发送端的滑动窗口受堵塞窗口大小的制约,不能大于堵塞窗口的大小。要想控制堵塞,发送端必须知道网络的状况,而TCP只能通过超时重传的情况来判断网络的状况(也有其他办法,比如发送探测报文等,但是可能会占用额外的网络资源),如果有大量的数据要重传就说明此时的网络大概率是发生堵塞了,要减小发送的数据量,当重传的数据量很小时,就说明此时的网络很有可能是畅通的,可以适当的增加数据。
  • 首先介绍一下慢启动。TCP连接开始时,cwnd设置为一个报文段(最大报文段SMSS的长度),一次就只能发一个报文段。当收到这一个确认是,cwnd加一,这样一次就可以发送两个报文段;当这两个的确认到达时,每一个确认增加cwnd加一,两个确认就加二,这样现在一次就能发送四个报文段,以此类推。可以看出这是指数增长。
  • 但是并不是无限制的增长下去,当cwnd增长到某个限度时就会降低增长速度,这就是堵塞避免。这个限度是ssthresh,初始值为65535,当cwnd达到这个值时,没收到一个确认,cwnd增加1/cwnd,假设cwnd=8个报文段时超过了ssthresh,那么当八个确认到达时,每个确认增加1/8cwnd,八个确认一共cwnd增加1,cwnd=9,所以cwnd的增长变成了线性增长。
  • 但是线性增长也是在增长,依旧可能使网络发生堵塞,而堵塞的后果就是丢包,当TCP发现需要进行超时重传时,它就认为网络可能发生了堵塞,需要减小数据的发送量,这个时候,就会将sshresh设置为cwnd/2,将cwnd设为1,重新进行慢启动,但是突然降低这么多,可能会造成网络卡顿。这个时候就可能用到快重传和快恢复算法。当接收端丢了一个中间包,再接收到后面的数据包时,发送的确认报文中的确认号依旧是对丢失的数据包之前的数据的确认,当TCP收到3个重复确认,就知道有一个中间包丢失了,就会立刻重传这个数据包,这就是快重传。TCP认为这种情况并不是很严重,不需要进行重新慢启动,此时TCP就会执行快恢复算法,cwnd减半,sshthresh=cwnd,并开始执行堵塞避免算法。这样就尽量避免了超时重传进而重新开始慢启动。
  • 最后再结合网络层路由器分组转发的功能,介绍一下全局同步现象的问题。TCP的数据报经过网络层、数据链路层的封装在网络中进行传输,其中会通过路由器来到达不同的网络,路由器的有缓存,当分组过多路由器来不及处理时,就会先存储在缓存中,由于缓存也是有限的,当缓存满了之后,以后到达的分组就会被丢弃,这就叫做尾部丢弃策略,这就会导致发送方触发超时重传,是TCP进入堵塞控制的慢开始状态,严重的是因为丢弃的报文很可能是来自不同的主机,这种丢弃就会同时影响到多台设备同时进入慢开始状态,这就叫做全局同步。全局同步使得全网的通信量突然下降了很多,而在网络恢复正常后又土壤增加很多。为了解决这个问题,就需要在缓存中的数据达到一个最小丢弃限度时,对后面到达的数据进行随机丢弃,当超过缓存容量时,依旧丢弃之后到达的所有数据。
  • 其实网络最好的状态应该是不需要中间设备的缓存,将时延降到最低,TCP BBR堵塞算法就是企图找到一个平衡点,通过不断地加快发送速度,将网络中的 管道填满,但是不填满中间设备的缓存,那么在这个平衡点就可以很好的达到高带宽和低时延的平衡。



5、流量控制和堵塞控制的区别

堵塞控制主要是避免过多的流量注入到网络中,避免链路或路由器过载,导致网络速度严重下降,面向的是整个网络。

流量控制主要是控制发送端的发送速度使得接收端来得及接收和处理,主要是面向端对端的。



四、TCP连接的建立和断开

TCP是面向连接的协议,所以TCP需要在信息传输之前建立连接,信息传输结束之后释放连接,而连接的建立和释放就是通过发送一些特定的报文来实现。

  • 连接的建立:建立连接的过程主要为的是解决3个问题,一是让通信的双方能够知道对方的存在,二是双方进行一些通信的准备(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量、同步连接双方的序列号和确认号),三是使双方可以对接下来的TCP通信分配一些资源(如缓存等)。TCP的连接采用客户服务器方式,主动发起连接的是客户,被动等待连接的是服务器。
  • 三次握手


    在这里插入图片描述
  • 第一次握手
  • 客户发送一个连接请求报文,同步号SYN=1,确认ACK=0,初始序号seq=x。这时TCP客户端进入SYN-SENT(同步已发送)状态。(SYN报文,即SYN=1的报文不能携带数据,但要消耗掉一个序号)。
  • 第二次握手
  • 当服务器收到连接请求报文时,如果同意建立连接,就会发送确认。请求确认报文中,同步号SYN=1,确认ACK=1,确认号ack=x+1,初始序号seq=y,发送之后服务端就会进入SYN-RCVD(同步收到)状态。(这个报文段也不能携带数据,但要消耗掉一个序号)
  • 第三次握手
  • 当客户收到服务器的确认,还要发送对其的确认。确认报文段中,确认ACK=1,确认号ack=y+1,序号seq=x+1。发送之后客户就会进入ESTABLISHED(已建立连接)状态。当服务器收到确认报文后也会进入这个状态。
  • 这样TCP连接就建立了,之后就可以按照前面介绍的传输机制进行传输信息了。当数据传输结束后,通信的双方都可以提出释放连接。


  • 四次挥手
  • 第一次挥手
  • 假设客户端先要断开连接。当客户端的应用进程认为自身已经没有要传输信息的必要时,就会提出了断开连接的申请,此时客户端的TCP就会发出一个连接释放报文段,并停止发送数据。连接释放报文段中,终止控制位FIN=1,序列号seq=u(等于前面已经传输过的数据的最后一个字节的序号加1),此时A进入FIN-WAIT-1(终止等待1状态)状态。
  • 第二次挥手
  • 当服务端收到连接释放报文段即发出确认,确认报文中,确认号ack=u+1,序号seq=v(等于前面已传送过的数据的最后一个字节的序号加一),然后B就进入CLOSE-WAIT(等待关闭状态),此时服务端就不会再接收信息,但是依旧可以发送信息,相当于此时TCP连接是半双工,半连通状态。客户端收到确认报文就会进入FIN-WAIT-2(终止等待2状态)状态,等待服务端的连接释放报文段。
  • 第三次挥手
  • 若服务端没有要发送的数据了,其应用程序就会通知TCP释放连接,此时服务端就会发出连接释放报文,其中终止控制位FIN=1,确认号ack=u+1,序号seq=w(之前发送过的最后的数据序号加1)。这时服务端就进入了LAST-ACK(最后确认状态)状态,等待客户端的确认。
  • 第四次挥手
  • 客户端接收到服务端发来的连接释放报文后,就会发出确认,确认报文中,确认ACK=1,确认号ack=w+1,序号seq=u+1(因为前面发送过的FIN报文要消耗一个序号)。然后进入到TIME-WAIT(时间等待状态)状态,等待一段时间后才会进入CLOSED状态。而服务器接收到确认报文后就会进入CLOSED状态。
  • TIME-WAIT状态:当客户端发送了确认报文后必须经过时间等待计时器设置的时间2MSL后才能真正的释放连接。时间MSL叫做最长报文段寿命,这个时间可以取不同值。让客户端等待这样一个时间主要是为了预防可能出现的2个问题,一是客户端发送的确认可能丢失,那么就可能需要重传,服务端一直收不到确认报文就会重传连接释放报文,那么此时进入时间等待状态的客户端就会重传确认报文。如果没有这个等待时间就不能保证客户端一定收到确认报文。二是防止之前提到的在网络中滞留的连接报文段出现在本连接中。客户端在发完最后一个ACK报文段后,经过2MSL时间,就可以使本连接持续的时间内产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现旧的连接请求报文。
  • 为什么要四次挥手不能三次挥手?因为断开连接时,首先提出断开连接的一方是不要发送数据了,但是另一方可能还要发送数据,所以第二次和第三次挥手就不能连在一起。



总结


因为TCP是在下层都是不可靠传输的基础上实现可靠传输,所以需要比较复杂的控制机制,同时为了提高传输的效率,保证传输的质量还需要进行流量控制、堵塞控制,整个学习下来感觉堵塞控制和滑动窗口是最复杂的。



版权声明:本文为justgo3原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。