原创文章首发于公众号:「码农富哥」,欢迎收藏和关注,如转载请注明出处!
TCP 是一种提供可靠性交付的协议。
也就是说,通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
但是在网络中相连两端之间的介质,是复杂的,并不确保数据的可靠性交付,那么 TCP 是怎么样解决问题的?
TCP 是通过下面几个特性保证数据传输的可靠性:
- 序列号和确认应答信号
- 超时重发控制
- 连接管理
- 滑动窗口控制
- 流量控制
- 拥塞控制
通过序列号和确认应答信号提高可靠性
如下图,在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知,这个消息叫做确认应答(ACK)。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
但是,如果在一定时间内发送端都没有得到确认应答ACK,发送端就会认为数据丢失,并进行数据重发。所以,即使产生了丢包,TCP仍然能保证数据能够到达对端,实现可靠的传输。
发送端确认应答ACK,主要分两种情况:
- 1. 发送端发送的数据丢包
上图中的主机A发出数据后因为网络拥堵等原因导致了丢包,数据无法达到主机B,此时,主机A如果在一个特定的时间间隔内都没收到主机B的ACK,则会将数据进行重发 - 2. 接收端发送的确认应答ACK丢包或延迟
这个图中主机A的数据正常发送到主机B,但由于网络堵塞等原因,主机B的ACK没有达到主机A。主机A在一定时间间隔内始终没收到ACK,则会重发这个数据。
此时,主机B收到数据后就会再次发送ACK,但是由于主机B已经接收过1-1000的数据,所以当再有相同数据达到它时就会放弃这个数据。
此外,也有可能因为一些其他原因导致ACK延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
虽然目标主机通过重发数据可以提供可靠的传输,但是对于目标主机来说,反复收到相同的数据可能会是一个”灾难“,既浪费网络资源,还要耗资源对它处理。
所以,我们需要一种机制来识别是否已经接收到了这个数据包、又能够判断数据包是否需要接收。
目标主机反复收到相同数据是不可取的,为了保持数据的一致性,目标主机必须扔掉重复的数据包,那么怎么判断该数据包是已经重复收取过呢? 为此我们引入了序列号。
序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
所以,通过序列号,上面说的“确认应答ACK处理”, “重发控制”,“重复控制”都能实现了。
超时重发如何确定呢?
-
重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔。如果超过这个时间仍未收到确认应答,发送端将进行数据重发。
最理想的是,找到一个最小时间,它能保证“确认应答一定能在这个时间内返回”。 -
TCP 要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间(RTT Round Trip Time)及其偏差(RTT波动的时间,也叫抖动)。将这个往返时间和偏差时间相加,重发超时的时间就是比这个总和要稍大一点的值。
-
重发超时既要考虑RTT往返时间,又要考虑网络抖动的偏差,如下图所示,网络网络环境不同可能会造成RTT大幅度摆动,TCP/IP的目的就是即使在这种环境下也能进行控制,不浪费网络流量。
-
在 BSD 的 Unix 以及 Windows 系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,最初其重发超时的默认值一般设置为6秒左右。
-
数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
-
此外,数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
连接管理
TCP是面向连接的通信协议,面向连接是指在数据通信之前先做好通信两端之间的准备工作。
因此,在数据通信之前,会通过TCP首部发送一个SYN
包作为建立连接和等待确认应答,如果对端发来确认应答ACK,则认为可以进行通信,否则如果对端没有发送正确的ACK应答,那么就不会通信。
另外通信完毕需要发送FIN
包来关闭连接
这就是我们常常说的 三次握手建立连接 和四次挥手关闭连接
我之前也写了一篇 一文彻底搞懂 TCP三次握手、四次挥手过程及原理,大家有兴趣可以去看看,了解TCP连接时如何建立和关闭的
TCP是以段为单位进行数据包的发送的
在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS,Max Segment Size),也就是一个段。最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。
TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送。进行重发时也是以 MSS 为单位。
MSS 在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小。然后会在两者之间选择一个较小的值投入使用。
上图的是Tcpdump抓包的信息,在三次握手建立连接时,大家都交换了对方的MSS,目的是告诉对方,我能适应每次TCP数据传输单位最大是多少,后面通信双方就会按照这个MSS大小作为发送单位发送数据,以上图为例,TCP每次传输最多不会超过65495字节
利用滑动窗口控制提高速度
上面说了,TCP 以1个段为单位,如果每发送一个段进行一次确认应答,才能进行下一次通信,那这样的传输方式有一个缺点,就是包的往返时间(RTT)越长通信性能就越低。如下图:
这种方式有点类似于数据库不能并发请求,只能一个挨一个的处理,自然这样的效率肯定是比并发低的
为解决这个问题,TCP 引入了窗口这个概念。确认应答不是以每个分段来确认,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。如下图所示:
如上图,我们假设窗口大小是4000字节,主机A可以一口气发送把4000字节的序列号发送完毕。这个跟前面每个段接收ACK后才能继续发送新一个段的情况相比,即使RTT变长也不会影响网络的吞吐量。
窗口大小就是指无需等待确认应答ACK而继续发送数据的最大值。
这种窗口机制实现了使用了大量的缓冲区(Buffer,指的是计算机存储收发数据的的内存空间),通过对多个段同时进行确认应答的功能。
滑动窗口示意图如下:
上面这个图一个段为1000字节,滑动窗口是4个段,在①的状态下,如果收到一个序列号为2000的ACK,那么2001 之前的数据就没必要重发了,这部分的数据可以被过滤掉,滑动窗口成为③的样子。
对于滑动窗口有以下几点特点:
- 上图中的窗口内的数据即便没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此,发送端主机需要设置缓存保留这些待被重传的数据,直到收到他们的确认应答。
- 在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
- 收到确认应答的情况下,将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也别称为滑动窗口控制。
滑动窗口控制与重发控制
在使用了窗口控制中,如果出现了丢包怎么办呢?这里我们还是分两种情况分析:
-
1.确认应答ACK未能正确返回的情况
在这种情况下,数据是已经被对端主机成功接收了的,是不需要进行重新发送的。
然而,如果在没有使用窗口控制的前提下,没有收到确认应答包的数据包都会被重发。
但是,在使用了窗口控制以后,就如下图所示,某些应答包即使丢失了也无需重发,这也提高了传输效率。
如上图所示,一个段大小为1000字节,一个窗口大小为6000个字节的情况,主机A连续发送了6000序列号的数据,中间的主机B对1001的ACK丢失了,但是后面的2001的ACK正常返回了,说明前2000的序列号的数据都正常读取了,那么即使1001的ACK丢失也不需要进行数据重发!
所以在窗口控制的机制下,前面的ACK丢失,也能通过下一个ACK进行确认,提高了不少效率。 -
2.某个报文丢失的情况
如果当接收端主机接收到一个自己应该接收的序列号之外的数据包时,它会一直对当前接收到的数据包返回确认应答包。
如上图所示,主机A的1001-2000序列号的报文丢失了,它会一直收到来自主机B的一个1001的ACK,这个ACK就像在跟主机A提醒 “我想接收从1001开始的数据”。当主机A连续收到这个1001的确认应答ACK 3次后,就会认为数据丢失了,需要重发。
在滑动窗口比较大的情况下,同一个序列号的确认应答将会被重复不断地返回。而发送端主机如果 连续 3 次 接收到同一个确认应答包,就会将其对应的数据重发,这种机制比之前提到的“超时重发”更加高效,所以被称之为“高速重发控制”
总结
TCP协议在实现传输可靠性上面做了很多:
- 通过序列号和确认应答信号确保了数据不会重复发送和重复接收。
- 同时通过超时重发控制保证即使数据包在传输过程中丢失,也能重发保持数据完整。
- 通过三次握手,四次挥手建立和关闭连接的连接管理保证了端对端的通信可靠性。
- TCP还使用了滑动窗口控制提高了数据传输效率
最后
文章如果对你有收获,可以收藏转发,这也是对我写作的肯定!另外可以关注我公众号「码农富哥」 (搜索id:coder2025),我会持续输出Python,服务端架构,计算机基础(MySQL, Linux,TCP/IP)的 原创 文章