原创文章首发于公众号:「码农富哥」,致力于分享后端技术 (高并发架构, 中间件, Linux, TCP/IP, HTTP, MySQL, Redis), Python 等原创干货和面试指南!
TCP 是一种提供可靠性交付的协议。
也就是说,通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
但是在网络中相连两端之间的介质,是复杂的,并不确保数据的可靠性交付,那么 TCP 是怎么样解决问题的?
TCP 是通过下面几个特性保证数据传输的可靠性:
由于篇幅有限,这个TCP协议可靠性的专题我分为上下两篇来写,这一篇先讨论前四个可靠性机制,剩下的流量控制和拥塞控制,会写到另外一篇文章,请关注。
关于TCP协议的文章还有几篇,大家也可以看看:
如下图,在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知,这个消息叫做确认应答(ACK)。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
但是,如果在一定时间内发送端都没有得到确认应答ACK,发送端就会认为数据丢失,并进行数据重发。所以,即使产生了丢包,TCP仍然能保证数据能够到达对端,实现可靠的传输。
发送端没有得到确认应答ACK的原因,主要分两种情况:
此外,也有可能因为一些其他原因导致ACK延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
虽然目标主机通过重发数据可以提供可靠的传输,但是对于目标主机来说,反复收到相同的数据可能会是一个”灾难“,既浪费网络资源,还要耗资源对它处理。
所以,我们需要一种机制来识别是否已经接收到了这个数据包、又能够判断数据包是否需要接收。
目标主机反复收到相同数据是不可取的,为了保持数据的一致性,目标主机必须扔掉重复的数据包,那么怎么判断该数据包是已经重复收取过呢? 为此我们引入了序列号。
序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
所以,通过序列号,上面说的确认应答ACK处理, 重发控制,重复控制都能实现了。
TCP是面向连接的通信协议,面向连接是指在数据通信之前先做好通信两端之间的准备工作。
因此,在数据通信之前,会通过TCP首部发送一个SYN
包作为建立连接和等待确认应答,如果对端发来确认应答ACK,则认为可以进行通信,否则如果对端没有发送正确的ACK应答,那么就不会通信。
另外通信完毕需要发送FIN
包来关闭连接
这就是我们常常说的 三次握手建立连接 和四次挥手关闭连接
我之前也写了一篇 一文彻底搞懂 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 之前的数据就没必要重发了,这部分的数据可以被过滤掉,滑动窗口成为③的样子。
对于滑动窗口有以下几点特点:
在使用了窗口控制中,如果出现了丢包怎么办呢?这里我们还是分两种情况分析:
如上图所示,主机A的1001-2000序列号的报文丢失了,它会一直收到来自主机B的一个1001的ACK,这个ACK就像在跟主机A提醒 “我想接收从1001开始的数据”。当主机A连续收到这个1001的确认应答ACK 3次后,就会认为数据丢失了,需要重发。
在滑动窗口比较大的情况下,同一个序列号的确认应答将会被重复不断地返回。而发送端主机如果 连续 3 次 接收到同一个确认应答包,就会将其对应的数据重发,这种机制比之前提到的“超时重发”更加高效,所以被称之为“高速重发控制”
TCP协议在实现传输可靠性上面做了很多:
如果大家有所收获的话
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。