【TCP专题】TCP的可靠性传输

        

目录

排序

MSS和MTU

分段与分片

序列号

确认机制

重传机制

RTT和RTO

快速重传机制

SACK

TCP“三次握手”和“四次挥手”中的可靠性保障

三次握手

第一个SYN报文段丢失

第二个SYN + ACK报文段丢失

第三个ACK报文段丢失

四次挥手

第一个FIN报文段

第二个ACK报文段

第三个FIN报文段

第四个ACK报文段


        一说到TCP,大家印象中出现的第一个词一定是“可靠”,所以,TCP到底是如何树立的“可靠人设”呢?我们下来就一起分析分析。

        TCP实现可靠性传输的手段总结起来就四样 --- 确认,重传,排序,流控

        流控无疑是一种传输可靠性的保障,因为不考虑对方承受能力的数据发送是不负责任的,也是不可靠的。当然,这一快的内容,我们后面会在单独的文章中去讨论。所以,这里,我们重点需要了解的就是确认,重传以及排序。

排序

        先来简单的把排序了解一下。我们一直说TCP这个协议可以实现分段操作。所谓分段,就是数据包数据量过大的时候,我们可以将其拆分成一个一个小的数据数据段来分别携带发送。之所以有这样的操作,其实本质上的原因是因为TCP这个协议本身就是一款基于字节流的协议。(这一点,我们在前面TCP连接建立中已经提到过了。)它在发送携带数据时,本身就是按照字节量来进行携带的。如果超过了它一个数据段所能携带的字节数,那自然是需要分段了。

        那一个TCP报文段所能携带的字节数到底是多少呢?这个其实是由一个参数MSS(最大段长度)来决定的。这个参数是需要在TCP建立握手的过程中,通过前两个SYN报文段来进行协商确定的。(在连接建立中说过,建立连接其实就是参数协商的过程,而这个参数就是需要双方协商的一个参数。)

双方协商过程中,MSS的值不一致的时候,会选择使用数值较小的作为之后传输分段的标准。

 

MSS和MTU

        MSS值的大小也不是随意而定的,其实也是受MTU(最大传输单元)的限制。当然,要弄清这一点之前,我们还是得先搞清这两个参数到底限定的是哪里的数据量。

什么是MTU?

MTU --- 最大传输单元,指的是数据链路层在封装成数据帧时所允许携带的最大数据量,在以太网当中,这个MTU的默认值为1500字节。

MTU --- 数据帧中携带的数据字节数。(对于数据帧而言,其数据包含网络层,传输层以及应用层封装及应用原始数据部分)

MSS --- 数据段中携带的数据字节数。(对于数据段而言,其数据仅包含应用层封装及其原始数据。)

分段与分片

        这里引入了一个新的概念叫做分片。分片其实和分段的作用是类似的,都是数据包中所包含的数据量过大的话,可以将其分成若干个小的数据包来分开传递。只不过,分片这个操作是由网络层的IP协议来执行的。(毕竟,IP协议加工完就需要移交到数据链路层进行进一步的封装了。所以,IP协议在将完成的数据包交给数据链路层之前,务必要保证其总长度要小于MTU值的限制)

        当然,如果在传输层使用TCP协议,因为TCP协议本身就可以分段,那如果分段完之后再交给网络层IP协议的时候,还需要再让IP协议进行分片,那这样就有点太浪费资源了。所以,为了保证一步到位,分段的时候这个MSS也会受到MTU值的影响。因为MTU的1500字节指的是IP头部 + TCP头部 + 数据部分(应用层头部加数据)组成。而MSS的长度特指的是这里说的数据部分。所以,MSS的典型值应该使用1500字节减去两个头部的长度。又因为IP头部和TCP头部长度都不是定长的,而他们最短字节数都是20字节。所以,MSS最大的典型值也就是1500 - 20 - 20 = 1460字节。

        分片和分段还有一个非常重要的区别点。那就是分片的时候,IP协议会直接将其数据部分的内容(即网络层之上的所有数据)进行分片,这些分片都只带有IP头部,之后,这些分片包在目标主机上再进行拼装还原成一开始的完整的数据报文。

(这是我使用PING命令,指定了携带数据量的大小,创造出来的一个分片环境)

(这里是其中一个分片的展开详情,我们发现,他IP头部封装的内容是被拆分后的数据)

        这样做有一个问题,就是因为IP协议本身并不是一个可靠的传输协议(他并不像TCP那样有确认,重传等机制),所以,但凡其中有一片丢失,到达目标主机之后,都无法还原成为完整的数据报文,需要依赖上层的协议(TCP或者应用层)的可靠性来重传。这一重传,那所有的分片就都需要重传了。这样做是非常低效的。

        所以,如果上层使用的是TCP协议,那么分段是势在必行的。每个分段,都是一个完整的TCP报文段,都具有TCP的头部,并且都会附加序列号进行排序。当某一个数据段丢失时,TCP也会进行重传,而重传时,也只需要重传这一个分段就可以了。

序列号

        上面也提到了,TCP将数据拆分为了不同的数据报文段,那自然接收时也需要还原成原始数据报文才行,那就需要有序号来讲不同的分段进行排序,最终按顺序还原。再TCP头部中就包含了一个序号字段,其作用就是保证分段的有序性的,也是我们TCP传输可靠的一个保障。

        其实之前的文章中已经说过这个序号了,我们可以再回顾一下。

        序列号 --- TCP本身是一种基于字节流的传输层协议(可以理解为TCP在传递数据时是以字节作为单位发送的)。而这个序列号就是建立在传送的字节流之上的。(并不是建立在传送的报文段之上,这一点很容易混淆)。也就是说,并不是一个报文段,序列号要加1,而是每发送一个字节,报文段就需要加1。所以,这个序列号其实就是字节流的编号

这里关于序列号的使用方法给一个小例子:

        假设,TCP的通信双方A和B。A想要通过TCP连接向B发送一个数据流。假设一共有5000个字节。而TCP协议在发送的时候,将这50000个字节的数据拆分为了多个数据段,每一个数据段长度为1000个字节。假定,数据流的首字节编号为0,那第一个报文段的序号取值应该是从0到999的。则第二个报文段分配的序号应该就是1000。其实每一个报文段使用的序号就是这个报文段中第一个字节所分配的序列号,以此类推。

(以上内容摘自01 - TCP连接建立)

        TCP的分段以及序列号的用法搞清楚了之后,排序的内容应该就已经比较完善了,后面我们再重点看下其他保证可靠性的手段。

 

确认机制

        确认和重传机制我们可以放在一起来说。首先是确认机制。这个是我们TCP保障可靠性的一个核心,因为在这浩瀚的网络世界中,一个数据包在其中迷失那是很正常的事情。所以,发出一个数据包和对方收到一个数据包是不能划等号的。如果你只是发出了,但是根本不去管别人有没有收到,那这么不负责任的表现肯定不能是一个可靠性协议能干出来的。而TCP协议保证对方能够收到本端发送的数据段的方法,就是让对方回复一个确认报文段,这就是确认机制的做法。

        这个确认报文段其最主要的标志就是TCP头部中的一个标记位ACK将置1,同时激活了确认序列号。确认序列号的做法,我们还是将之前在TCP建立连接中说过的内容拿来回顾一下。

        确认序列号 --- TCP为了保证传输的可靠性,所以,加入了确认机制。即接受方每收到发送方发送的数据时,需要回复一个确认报文(当然,TCP是一种全双工的通讯协议,接受方也是可以给发送方传递信息的,所以这个确认报文中是允许携带数据的。)确认报文中,这个确认序列号就至关重要了,它表明的是接受方期望收到发送方发送的下一个字节的序号。同时也代表接受方已经收到了确认序号之前的所有字节,这种确认模式我们称为累积确认

这里关于确认序列号也给出一个小例子:

        假设,通信双方还是A和B。现在,A给B发送了一个报文段,这个报文段的序列号假设为0,这个报文段所包含的数据部分的字节数假设为1000字节。(就是上个例子中第一个报文段的数据被发送了过来)。B收到该数据段之后,按照要求,需要回复一个确认报文,这个确认报文中的确认序列号应该指示的是B想要的下一个字节的序号,则因该就是1000。(第二个数据段第一个字节的序号)。当然,同时它也代表B已经收到了1000之前所有的字节。则A收到B的确认报文之后,下一个数段就会去携带序号从1000开始的第二段数据。

(以上内容摘自01 - TCP连接建立)

        当然,为了保证资源的充分利用,在TCP连接建立完成之后,确认报文段中也是可以携带数据的,相当于发送数据的同时也顺便确认了自己收到的数据。

重传机制

        那如果在本端发送完数据段之后,并没有收到对端反馈的确认报文呢?这时候,就需要使用到重传机制了,既然我已经有理由怀疑你没有收到,那我再发你一遍不是最基本的操作吗?

RTT和RTO

        重传机制中有一个很重要的点,就在于到底多长时间没有收到你的确认报文段,我才去重传。这种重传机制我们称为超时重传。(重传不一定只是在超时的情况下出现,这个我们后面再看)

        当然,超时重传的这个时间是非常重要的一个参数,那这个时间到底应该设置为多少呢?我们需要先理解一个和时间相关的参数 --- RTT(Round-Trip Time)往返时间

        RTT往返时间 --- 指的是发出端将数据发出后,到他接收到对端反馈的确认报文之后的这一段时间。

        超时重传的这个重传时间,我们称为RTO(Retransmission Timeout 超时重传时间)。这个时间就是我们这里需要仔细关注的时间,而这个时间和我们刚才说的这个RTT往返时间是有密切关系的。

        这里,我们可以思考下,这个重传时间过长或者过短,会发生哪些情况。

        先看第一种情况,就是如果当超时时间比较大。其实这种情况带来的问题非常显而易见,那就是丢包之后,重传的效率会降低,无法及时响应。

        那如果重传时间设置的过小呢?如果这个时间比较短,甚至比我们RTT时间还要短,那就有可能造成不必要的重传,可能人家也正常收到了数据包,也正常回复的确认报文,只是因为这个报文还没有到达本端,本端就重新发送一遍。这样不也是资源的浪费嘛。

        所以这个RTO的取值应该略大于RTT,但是也不能大太多。

        这个值具体是需要通过RTT来进行计算的,并不是一个固定值。因为RTT的时间也并不都固定。至于其中的算法,我们就不去过细的研究了,我们记住这个RTO是一个动态变化的值就可以了。

        在这个超时重传中,还有一个非常有趣的机制,就是超时间隔加倍

        拿上边的图举个例子,客户端发送序列号为100的一个数据段,服务器收到后,正常的回复ACK确认报文。但遗憾的是,这个确认报文在网络传输过程中,被淹没在数据洪流中了。客户端并没有收到服务器反馈的确认报文段。于是,在等待了RTO一个超时间隔后,客户端决定重发序列号为100的这个报文段。但是,这次的情况和上次一样,服务器回复的ACK依然没能正常的到达客户端。客户端只能继续等待超时间隔之后重发。只不过,这次的超时间隔时间将是第一次的2倍。也就是说假设第一次RTO = 0.5S,则第二次的RYO = 1S。

        如果运气不好,重传的数据包还是没有收到确认的话,则这个翻倍机制将继续延续,下次的RTO = 2S了。(注意,这里都是重传同一个数据包的时候出现的情况哈。)

        这个机制设计出来主要是和拥塞控制有点关系。这里的逻辑我们可以稍微盘一下。因为定时器超时导致的重传,最有可能造成这样结果的就是网络环境拥塞导致的。在这样的拥塞情况下,如果还是不停的去重传报文段,只会使拥塞加重。所以,TCP采用了这种超时间隔加倍的方式,来缓解这一点。

快速重传机制

        超时重传的问题就是这个超时间隔会越来越长,这样超长时间的重传间隔,会加重端到端之间的时延。

        好在,在TCP中,发送方可以通过接收方的反馈,在超时时间到达前,意识到数据包丢失的现象发送了,并进行重传。

        这种情况出现在接受端收到一个失序报文的情况下。

        什么是失序报文呢?简单来说,就是接受方在收到一个数据段中的序列号大于自己期望的序列号,这样的报文就是一个失序报文,这就说明自己期望的报文可能在茫茫网海中丢失了。

        用上边的图再说明一下这个失序报文的问题。客户端发送了序号为100,200,300,400的报文,之后等待服务器的确认。(注意,在TCP中并不是严格的只发一个报文等待对方回复,这样效率太低,所以,我们会一次性多发几个报文再等待回复,这个发送量和窗口值有关,我们在流控的时候会详细讲解。)服务器收到序号为100的报文之后回复ACK,其中的确认序列号为200。可是,之后服务器这边并没有如愿的收到序号为200的数据报文段,而是收到了序号为300的报文段。那这种情况对于服务器来说,就是接收到了失序报文了。

        这种情况下,服务器将意识到自己期望的报文段丢失了,所以,他不能直接确认后面的报文段,因为我们知道,TCP的确认是累计确认,也就是说,如果客户端收到服务器发送的确认序号为300的数据报文段,则客户端会任务300之前的字节流均已经传递完毕,并被接收了。则他将意识不到此时报文段丢失的问题。所以,这种情况下,服务器需要想办法让客户端知道报文段已经丢失了。但是,TCP不能直接发送一个否定确认(就是指没有办法直接发送一个数据报文段,告诉对端自己哪个报文没有接收到。)所以,TCP将采用冗余ACK(Duplicate ACK)的方式来完成这次通告。

        所谓冗余ACK就是服务器将会通过再次发送携带确认序列号未丢失报文序号的确认报文,并且连续发送三次。(如上图所示)TCP就是通过这种方式来告知对端,这个报文已经丢失了,期待对方重传。

        当客户端这边接收到3次冗余ACK之后,将意识到此时序号为200的报文段已经走失,需要重传。注意,这个时候的重传并不是因为RTO时间到达而触发的,这种重传是已数据包为驱动的一种重传机制,我们将这种重传称为快速重传机制

        这种重传会在定时器超时之前重传报文段,就可以解决超时重传时延加重的问题。

        下面的图是抓包看到的快速重传的现象图。

SACK

        在快速重传机制中,还存在一个问题。我们前面说过,TCP的确认是累积式的,正确接收但失序的报文段是不会被接受方逐个确认的。因此,发送方在重传的时候,需要将冗余ACK确认及之后的报文段都重新发送一遍才行。(用上面的图举例子,就是需要重传序号为200及200之后的所有报文段)。

        这样的机制,很明显会导致资源的浪费,后面传递的这些报文段,相当于都是无用功,因为前面已经发送过一次了。(当然,他也确实不知道后面的报文有没有被接收到)

        为了优化这种情况,TCP有一个选择确认(selective acknowledgment)机制,我们一般就写作SACK。在确认报文中,他会增加一个变量在TCP头部的选项字段中。里面携带的就是自己已经收到的数据信息。将这个信息传递给发送方,这样,发送方在重传的时候,只需要重传丢失的报文段即可,而不用重传之后所有的内容。

(一个冗余ACK中所包含的SACK内容示意,这里面所写的序列号范围就是已经接收到的序列号范围,这个数据包要求重传的序号是3060636970,而已经收到的范围是3060658150-3060672270,所以,重传时只需要传3060636790-3060658150这一段的内容即可。)

        当然,这个SACK机制是否可以执行,也是需要通信双方实现就协商好的。协商的位置还是在三次握手中前两次SYN报文中。(目前,大部分系统都是默认支持并开启这个功能的)

TCP“三次握手”和“四次挥手”中的可靠性保障

        “三次握手”和“四次挥手”的过程我们都已经非常清楚了,不过我们在前面两个篇章中给大家说的都是很顺利的理论过程。在真实的网络世界中,这一切就不一定那么顺利了,发送过程中的任何一个数据包都有可能丢失,所以,我们就具体看下这些数据包如果丢失了,TCP是如何应对的。

三次握手

        那还是从连接建立的三次握手的过程看起,我们先上图回顾正常的三次握手的过程。

        常规的过程我们就不再赘述了,我们主要关注一下,这三个数据包中,任何一个数据包丢失会造成啥样的影响。

第一个SYN报文段丢失

        客户端首先会发送第一个SYN报文,之后进入到SYN_SENT状态,这个状态就是在等待对方的ACK应答。但是,如果本身SYN报文丢失,则无法等到服务器回复的ACK。

        这种情况将触发超时重传机制,RTO时间到了,则将再次发送SYN报文段。(因为这是第一个数据包,还无法根据RTT来估算RTO,所以,一开始的RTO是一个定义值,根据不同系统设置可能不同,一般推荐时间为1s。)。重传的SYN报文段的所有内容均和之前的一样。

        当然,重传的SYN如果接收到了服务器的SYN + ACK报文,则也将进入到ESTABLISHED状态。但是,也可能出现再次丢失的情况,那还将再次重传。只不过,第二次的重传时间就是2倍的RTO了(超时间隔加倍)。

        如果SYN报文一直丢失,也不会一直重传下去,系统一般会设置最大的重传次数,当达到次数后,再在重传时间内没有收到ACK则将直接关闭连接。(重传次数根据系统设置来定)

第二个SYN + ACK报文段丢失

        第二次握手的这个SYN + ACK报文段其实有两重意义。一方面,ACK是为了确认第一个SYN数据包;当然,另一方面,也是服务器为了发送请求建立连接的请求。

        正是因为他的作用比较丰富,所以,一旦这个数据报文段丢失,则产生的现象也比较有意思。

        首先,因为这个报文段起到了确认客户端SYN的作用,所以,他如果丢失,则客户端将无法确认服务器是否受到了自己发送的请求。则会触发超时重传,重新发送SYN。

        而这个报文段本身也是也是服务器发送的请求,所以,他也在期待客户端发送ACK对自己进行确认。但是,因为这个报文段丢失,所以,他也不可能收到客户端的确认。那他也将触发超时重传。

        所以,这个报文一旦丢失,两端都将跟着忙活。

        那如果这个SYN + ACK的报文一直丢失,客户端和我们上面第一种情况是相同的,达到重传次数后,将关闭连接。其实服务器这端也是一样的,在达到重传次数后,也将进入到CLOSE(关闭)状态。

第三个ACK报文段丢失

        最后这个ACK报文是客户端在收到服务器发送的SYN之后发送的,发送完成,客户端将进入到ESTABLISHED(建立完成)状态。但是,服务器一直没有收到这个确认报文,则将触发超时重传,重传第二个数据包SYN + ACK报文。

        这次重传要不就是接收到客户端发送的ACK,建立完成;要不就是达到最大重传次数,导致服务器端连接关闭。

四次挥手

        还是先用之前我们用过的图来回顾一下四次挥手的过程。

        下来,我们也是分别关注一下这四个数据包丢失造成的影响。我们这个场景中还是假设客户端作为主动关闭方。

第一个FIN报文段

        客户端发送完第一个FIN报文之后,将进入到FIN_WAIT1的状态,在这个状态中,如果能够正常收到服务器发送的ACK报文,则可以进入到FIN_WAIT2的状态。

        当然,如果这个数据包丢失的话,他自然也无法收到服务器反馈的ACK。则将触发超时重传。重传之后收到ACK则将继续向下切换状态。但是,如果这个数据包持续丢失,则重传时间将间隔翻倍,最终,达到最大重传次数后,进入CLOSE(关闭)状态。

第二个ACK报文段

        服务器收到FIN之后,将回复ACK报文作为应答。回复完成将进入到CLOSED_WAIT状态。如果这个ACK报文对方没有收到,注意,ACK报文段是不会重传的,则客户端将超时重传FIN报文段。

        如果,这个ACK报文段一直丢失,则客户端重传FIN达到最大次数后,也将进入CLOSE(关闭)状态。

第三个FIN报文段

        服务器发送的这个FIN报文如果客户端没有收到,服务器也就无法收到客户端最后回复的ACK应答,则服务器将触发超时重传。

        如果,这个FIN报文段一直丢失,则服务器重传FIN达到最大次数后,将进入CLOSE(关闭)状态。

第四个ACK报文段

        其实最后一个ACK报文段的情况也是差不多的,我们专门设计了主动段开放再回复最后一个ACK之后,需要等待2MSL的机制。所以,客户端最后一个ACK如果服务器没有收到,则将触发服务器的超时重传,重传FIN报文。

        客户端在收到这个FIN报文后,会重新反馈ACK报文,并且重置2MSL的计时器时间。

        如果,这个ACK报文段一直丢失,则服务器重传FIN达到最大次数后,将进入CLOSE(关闭)状态。而客户端再没有收到新的FIN后,等待2MSL时间到达后也将进入CLOSE(关闭)状态。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

临界~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值