计算机网络 TCP协议梳理

前言

本文梳理TCP协议
TCP:Transmission Control Protocol ,传输控制协议

TCP的主要特性

  • 面向连接的传输协议
    是指应用程序在使用TCP之前,必须先建立TCP传输连接,在传输数据完毕后,必须释放已建立的TCP传输连接。
  • 仅支持单播传输
    每条TCP传输连接只能有两个端点(socket),只能进行点对点的数据传输,不支持多播和广播的传输方式。
  • 提供可靠的交付服务
    TCP可以无差错、不丢失、不重复,且按时序达到对端。
  • 传输单位为数据段
    TCP仍采用了传统的"数据段"作为数据传输单元。
    数据段指TCP对从应用层接收的数据进行分割所得到的数据块。
  • 仅一种TPDU格式
    因为在TCP数据段头部已包括了各种TPDU所需的特征字段,主要是通过其中的多个控制为来实现的。
  • 支持全双工通信
    TCP连接的两端都设有发送和缓存,用来临时存放双向通信的数据。
  • TCP连接是基于字节流的,而非报文流
    TCP不像UDP那样以一个个报文独立的进行传输,而是在不保留报文边界的情况下以字节流方式进行传输。
  • 每次发送的TCP数据段大小和数据段数都是可变的
    TCP传输的数据段大小是根据对方给出的窗口大小和当前可用的发送窗口(当前网络的拥塞程度)来决定的;同时也受应用层传送的报文大小和所途径网络中MTU值大小决定;
    这样一来每次可以发送的TCP数据段数也是不固定的;可以一次仅发送一个TCP数据段,也可以一次发送多个TCP数据段,只要在当前可用的发送窗口大小限制之内即可。
    另外如果应用进程传送到TCP缓存中的数据太长,TCP可以对它进行分段;反之,如果传到TCP缓存中的数据太小,则TCP会等待缓存中有足够多的数据后再组装成一个数据段一起发送(沾包和拆包)。

TCP报文段

报文

ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;
ack、seq小写的单词表示序号。

  • 源端口和目的端口
    分别代表呼叫方和被叫方的TCP端口号。一个端口与其主机IP地址就可以完整的标识一个端点了,即Socket。
    端口是传输层的概念,源ip和目的ip会在网络层添加到IP首部中。

  • 序列号seq:
    占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

  • 确认号ack:
    占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;
    序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

  • 同步SYN:连接建立时用于同步序号。
    当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。
    因此,SYN=1表示这是一个连接请求,或连接接受报文。
    SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。

  • 确认ACK:
    占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效

  • 终止FIN:
    用来释放一个连接。
    FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接

  • 窗口大小
    指示发送此TCP数据段的主机上用来存储传入数据段的窗口大小,也即发送者当前还可以接收的最大字节数。“窗口大小”字段的值告诉接收本数据段的主机,从本数据段中所设置的“确认号”值算起,本端目前允许对端发送的字节数,是作为让对方设置其发送窗口大小的依据。

为什么TCP是可靠的连接

校验、序号、确认、重传。

  1. 字节编号机制
    TCP数据段是以字节为单位对数据段中的数据部分进行一一编号,确保每个字节的数据都可以有序传送和接收。
    TCP发送的数据段中“数据”部分(不包括TCP数据段头部),每个字节都有一个序号,每个数据段中的“序号”字段是以该数据段中第一个字节的序号进行填充的。

  2. 数据段确认机制
    TCP要求每接收一个数据段都必须由接收端向发送端返回一个确认数据段ACK,其中的确认号表明了接收端已正确接口的数据段序号(确认号前面的所有数据段)。
    ACK是一个表明“确认号”字段是否有效的标志位。只有ACK字段的值为1,数据段中的“确认号”才有意义,否则数据段中的“确认号”没有意义,即不具有上面所说的“确认号”含义。

    • TCP可一次连续发送多个数据段
      TCP不需要等待接收对方发送的确认数据段(“ACK”字段为1的数据段)就可以一次性连续发送多个数据段,这样可大大提高数据发送效率。但一次性可发送多少个数据段是受对方返回的“窗口大小”字段值和当前可用“发送窗口”大小双重限制的。因为发送端对还没有收到确认的数据段要进行缓存,这需要占用一定的“发送窗口”大小。
    • 仅对连续接收的数据段进行确认
      假设每个数据段的长度大小均100字节,接收端收到了序号为1、101、201、401四个数据段。其中序号为301的数据段暂时没收到,此时接收端返回的确认数据段中的“确认号”只能是301,而不会是501,也就是只对前三个数据段进行确认,不会对后面的401数据段进行确认,因为中间的301数据段还没收到。当后面收到了301数据段后,可能会返回一个“确认号”为501的数据段,这时就代表301和401数据段均已正确接收了。
    • 不连续序号的数据将先缓存
      如某主机先后接收到了对端发来的序号分别为1、101、201、301、601、401、801、501的数据段(假设数据段大小均100字节),则该主机首先把1、101、201、301这四个数据段向应用层提交,并向发送端发送一个“确认号”为401的确认数据段,从而可以从“接收窗口”中删除这四个数据段,释放“接收窗口”;然后再把601、401、801这三个数据段先缓存在“接收窗口”中,直到接收到501号数据段,再按401、501、601顺序重组并向应用层提交,接着发送一个“确认号”为701的确认数据段,从而又可以从“接收窗口”中删除这三个数据段,释放“接收窗口”,但此时“接收窗口”中仍缓存有801号数据段,因为701号数据段还没有得到确认。
  3. 超时重传机制
    TCP中有一个重传定时器,在发送一个数据段的同时也启动了该定时器。如果在定时器过期之前该数据段还没有被对方确认的话,则定时器停止,重传对应序号的数据段。
    TCP采用自适应算法,动态的改变超时重传时间。
    同时也有快重传机制,可以不用等到计时器超时就可立即发送。

  4. 选择性确认(Selective ACK,SACK)机制
    在SACK支持下,仅可以重传缺少部分的数据,而不会重传那些已正确接收的数据。
    假设接收端已收到1、101、201、401、501这五个序号的数据段,在发送确认号为301的确认数据段时,在SACK扩展选项中标记401(起始序号为401,结束序号为500)和501(起始序号为501,结束序号为600)这两个不连续的数据段。这时发送端就会知道,不需要再发送401和501这两个数据段了,只需发送301号数据段即可。这样大大节省了网络资源,也提高了数据传输效率。

流量控制

流量控制是基于通信双方的数据发送和接收速率匹配方面考虑的,其最终目的就是不要让数据发送得太快,以便接收端能够来得及接收,是一个链路两端的点对点行为。即是避免发送方发送数据过快,要让接收方来得及接收。

  • 流量控制的目的:就是不要让数据发送得太快,以便接收端能够来得及接收

  • 流量控制的方案:TCP利用滑动窗口机制来实现流量控制

  • 在通信过程中,接收方根据自己接收缓存的大小,动态调整发送方的发送窗口大小,即接口窗口rwnd(接收方设置确认报文段的窗口字段来将rwnd通知给发送方),发送方的发送窗口取接收窗口rwnd拥塞窗口cwnd的最小值。

  • 持续计时器
    TCP为每一个连接设置一个持续计时器,只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口的探测报文段,接收方接收到探测报文段时给出现在的窗口值。若窗口仍然是0,那么发送方就重置持续计时器。
    image.png

拥塞控制

  • 出现拥塞的条件:对资源的需求总和 > 可用资源
    拥塞控制是基于网络中各段链路的带宽和中间设备数据处理能力方面而考虑的,不要使网络中出现数据传输阻塞,也就是不要让发送端发送的数据大于接收端数据处理能力,是一个端到端的行为。
    发生拥塞的原因可能很复杂。如TCP连接的整个链路中有结点设备的缓存空间太小、数据转发能力太低、某段链路带宽太小、对端数据接收能力低等,都可能引起网络拥塞。
    例如,用户想单方面地提高中间路由器结点的数据转发能力,而忽视了所经路径上各段链路的带宽,结果是虽然提高了路由器转发性能,也提高了数据转发速度,但也同时造成了在链路上排队前行的数据不断增多,这样不仅没有解决网络拥塞问题,反而使网络拥塞得更严重。
    再如,用户想单方面地提高路由器的缓存能力,但没有同步考虑路由器的数据转发能力和链路的带宽,结果是虽然可以使更多的数据在路由器上暂时缓存,但在缓存中排队的数据所需等待的时间会更长,因为队列比以前更长了,结果会因为超时重传这些数据。重传数据越多,网络负荷超越重,最终导致拥塞更加严重。

  • 拥塞控制的目的:防止过多的数据注入到网络中

  • 拥塞控制方案为:
    慢启动、拥塞避免;快重传、快恢复

  • 发送窗口=min(接收窗口rwnd,拥塞窗口cwnd)
    接收窗口:接收方根据接受缓存设置的值,并告知给发送方,反映接收方容量。
    拥塞窗口:发送方根据自己估算的网络拥塞程度而设置的窗口值,反映网络当前容量。

慢启动和拥塞避免

慢启动是指为了避免出现网络拥塞而采取的一种TCP拥塞初期预防方案。其基本思想就是在TCP连接正式传输数据时,每次可发送的数据大小拥塞窗口是逐渐增大的,也就是先发送一些小字节数的试探性数据,在收到这些数据段的确认后,再慢慢增加发送的数据量,直到达到了某个原先设定的极限值慢启动阈值SSTHRESH
拥塞窗口大小CWND再次大于或等于SSTHRESH时,启动“拥塞避免”解决方案。它的基本思想是在CWND值第二次达到SSTHRESH时,让“拥塞窗口”大小每经过一个RTT(一个数据段往返接收端和发送端所需的时间)时间仅值加1(即新的CWND只增加一个MSS大小,而不是原来CWND的几倍),使其以线性方式慢慢地增大,而不是继续像“慢启动”方案中那样以指数方式快速增大。显然,这种CWND增长速度明显要慢于“慢启动”方案中的CWND增长速度。当再次发生数据丢失时,又会把SSTHRESH减为当前CWND的一半,同时把CWND置为1,重新进入“慢启动”数据发送过程,依此类推。

慢启动指数规律增长,直到达到慢启动阈值SSTHRESH,进行拥塞避免加法增大(线性),发生数据丢失时把SSTHRESH乘法减小为原先的一半,同时把cwnd置为1,重新进入慢启动阶段。

image.png

快重传和快恢复

  • 快重传
    当接收端收到一个不是按序到达的数据段时,TCP实体迅速发送一个重复ACK数据段,而不用等到有数据需要发送时顺带发出确认;在重复收到三个重复ACK数据段后,即认为对应“确认号”字段的数据段已经丢失,TCP不等重传定时器超时就重传看来已经丢失的数据段。
    image.png

  • 快恢复
    快速恢复算法的基本思想:在收到第三个重复ACK时,把当前CWND值设为当前SSTHRESH值的一半,以减轻网络负荷,然后执行前面介绍的“拥塞避免”算法,使CWND值慢慢增大,以避免再次出现网络拥塞。
    image.png

参考:
TCP流量控制、拥塞控制

TCP三次握手

三次握手

  1. 建立连接时:
    客户端发送syn包(seq = x, SYN = 1)到服务器,并进入SYN_SENT状态,等待服务器确认。

  2. 服务器收到syn包:
    必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK包(seq = y,ack = x + 1,SYN = 1, ACK = 1),此时服务器进入SYN_RECV状态

  3. 客户端收到服务器的SYN+ACK包:
    向服务器发送确认包ACK(seq = x + 1, ack = y + 1, ACK = 1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手

四次挥手

  1. 客户端进程发出连接释放报文(seq = u, FIN = 1),并且停止发送数据。
    此时,客户端进入FIN-WAIT-1(终止等待1)状态
    TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

  2. 服务器收到连接释放报文,发出确认报文(seq = v, ack = u + 1, ACK=1)
    此时,服务端就进入了CLOSE-WAIT(关闭等待)状态
    TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

  3. 客户端收到服务器的确认请求后,客户端就进入FIN-WAIT-2(终止等待2)状态
    等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文(seq = v + 1, ack = u + 1, FIN=1, ACK = 1),由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

  5. 客户端收到服务器的连接释放报文后,必须发出确认(seq = u + 1, ack=w+1, ACK=1),并进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的报文后,才进入CLOSED状态。

  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

TCP Fast Open介绍

image.png

  • 第一次
    用户向Server发送SYN包并请求TFO Cookie;
    Server根据用户的IP加密生成Cookie,随SYN-ACK发给用户
    用户储存TFO Cookie

  • 再次:
    用户向Server发送SYN包(携带TCP Cookie),同时附带请求;
    Server校验Cookie(解密Cookie以及比对IP地址或者重新加密IP地址以和接收到的Cookie进行对比)。
    如果验证成功,向用户发送SYN+ACK,在用户回复ACK之前,便可以向用户传输数据;(1RTT)
    如果验证失败,则丢弃此TFO请求携带的数据,回复SYN-ACK确认SYN Seq,完成正常的三次握手。

  • TFO 的两大好处:
    提高网络利用率(三次握手期间是可以传输应用数据的,1RTT)
    提高网络安全性(可以防止SYN泛洪攻击)。

糊涂窗口综合症(Silly Window syndrome)

基于窗口的流量控制方案,如TCP所使用的,会导致一种被称为“糊涂窗口综合症 SWS(Silly Window Syndrome)”的状况。如果发生这种情况,则少量的数据将通过连接进行交换,而不是满长度的报文。
该现象可发生在两端中的任何一端:接收方可以通告一个小的窗口(而不是一直等到有大的窗口时才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个大的报文段)。可以在任何一端采取措施避免出现糊涂窗口综合症的现象。

这个问题可以归结为小包的问题,就是由于发送端和接收端上的处理不一致,导致网络上产生很多的小包,之前也介绍过避免网络上产生过多小包的措施,比如Nagle算法。在滑动窗口机制下,如果发送端和接收端速率很不一致,也会产生这种比较犯傻的状态:发送方发送的数据,只要一个大大的头部,携带数据很少。
对于接收端来讲,如果接收很慢,一次接收1个字节或者几个字节,这个时候接收端 缓冲区很快就会被填满,然后窗口通告为0字节,这个时候发送端停止发送,应用程序收上去1个字节后,发出窗口通告为1字节,发送方收到通告之后,发出1个字节的数据,这样周而复始,传输效率会非常低。
同时如果发送端程序一次发送一个字节,虽然窗口足够大,但是发送仍是一个字节一个字节的传输,效率很低

参考:
速读原著-TCP/IP(糊涂窗口综合症)
TCP-IP详解:糊涂窗口综合症

Q&A

1. 为什么连接的时候是三次握手,关闭的时候却是四次握手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。
但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

2. 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
在Client发送出最后的ACK回复,但该ACK可能丢失。**Server如果没有收到ACK,将不断重复发送FIN片段。**所以Client不能立即关闭,它必须确认Server接收到了该ACK。
Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。
如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。

3. 如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

4. 为什么不能用两次握手进行连接?

首先我们要知道信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。
在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。
如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

参考:
TCP的三次握手与四次挥手理解及面试题

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/112340850