TCP的主要机制 | 网络编程

目录

        1,确认应答

        2,超时重传

        3,连接管理

                1)三次握手

​编辑       

                2)四次挥手

        4,滑动窗口

        5,流量控制

        6,拥塞控制

        7,延时应答

        8,捎带应答

        9,黏包问题

        10,异常处理

        总结


        概要:TCP为了实现可靠性,使用了确认应答机制和超时重传机制,这两个机制是TCP最核心的机制。基于确认应答机制,TCP在建立连接上就体现出了三次握手这样的形式,在释放连接上则是体现出了四次挥手。实现了可靠性,那就意味着牺牲了传输效率。为了提高效率,TCP采用了滑动窗口机制实现批量的发送数据,减少等待ACK的时间。而滑动窗口每次批量发送数据的大小则由流量控制和拥塞控制来共同决定。为了能再提高效率,TCP使用了延时应答。基于延时应答,就可以实现捎带应答,减少信息的发送次数,降低计算机的负荷。TCP面向字节流的特性引发了粘包问题,通过对异常的处理实现了心跳包的机制。

        1,确认应答

        确认应答是TCP协议最核心的两个机制之一,在TCP中,当发送方的数据到达接收方时,接收方会向发送方返回一个消息表示自己已经接收到了这条数据了,这条消息就叫做确认应答(ACK)。

        举个例子,就好比你跟别人打电话时,你每说几句话后对方都会“嗯嗯”一下来回应你,表示他已经有在听或者听到了你说的东西,这就相当于对你说的内容(发送的数据)进行一个确认(确认应答)。

                         TCP的主要机制 | 网络编程_第1张图片

        TCP通过确认应答机制实现了可靠的数据传输。发送方发送了数据后,会等待接收方返回的确认应答(ACK)。如果有应答,就表示数据成功到达接收方并被其获取。在等待一段时间后,如果没有收到接收方的ACK就表示发送的数据很有可能在途中丢失了,此时发送方就会重新发送一次数据,并再次等待接收方的ACK。通过这样,TCP实现了可靠的数据传输。

        在TCP传输中发送方是批量发送数据(后文会讲到),但由于网络可能存在延时等情况,此时就有可能出现“后发先至 ”的情况。

        此时就要引入序列号来解决先后问题。接收方根据序列号返回确认应答号,此时发送方就可以根据确认应答号明白哪条消息没有发送成功。需要注意的是序列号是针对每一个字节进行编号,并且序列号跟确认应答号是不一样的,确认应答号的序号是发送方发过来的所有数据的最后一个字节序号的下一个,例如发送方发送了 0 - 1000 个字节的数据(假设数字就是序号),此时接收方返回的确认应答号就是1001,表示要索求从1001开始的数据。

        2,超时重传

        超时重传是TCP协议最核心的两个机制之一。在网络的传输过程中,不仅发送的数据可能会丢失,接收方返回的ACK也可能是会丢失的,此时如果发送方迟迟接收不到ACK,就会认为刚才发送的数据在网络中丢失了,就会再次发送一次数据。

        如果是第二种丢失情况即接收方已收到数据但返回的ACK在途中丢失,那么此时发送方重新发送数据的话,接受方就会收到大量相同的重复数据,对于这种情况,TCP会在接收缓冲区里根据已收到的序列号自动去重。

        如果重新发送了数据后还是没有收到ACK,那么此时发送方会做的仍然是超时重传,但是每次重传之间的时间间隔会不断增大,等待的时间以2倍,4倍这样指数增长。如果重传了一定次数后仍没有获得确认应答,此时TCP就会尝试重新配置连接,如果失败,就表示对端主机出现异常,不再等待,关闭连接。这一行为体现了TCP的另一个特性:尽最大可能完成传输。

        总结:如果通信过程一切顺利,则用确认应答保证可靠性。如果出现网络丢包,就用超时重传作为补充。这两个机制是TCP保证可靠性的基石。

        3,连接管理

                1)三次握手

        三次握手是通信双方建立连接的过程。三次握手不是保证TCP可靠性的核心机制,但却是最知名的TCP机制。

首先了解几个概念:

        握手:是指通信双方进行了一次网络交互(记录对方信息)

        SYN:同步报文段。意思是一方向另外一方发送了一个请求:申请建立连接。

        在TCP中,第一个发送SYN的被称为客户端,接收第一个SYN的被称为服务端。

                                           TCP的主要机制 | 网络编程_第2张图片

         参考上图,A向B发送了一个SYN。B收到了A的连接请求,将会返回一个ACK,同时B也要跟A建立连接,所以会发送一个SYN。A收到B的SYN后,再向B返回一个ACK表示已收到请求。这样的交互过程一共要经历三次,所以称为 三次握手。

       三次握手可以理解为一次投石问路的过程,测试接收方和发送方各自的发送能力和接收能力是否正常,如果正常,那么接下来的通信过程也就能顺利进行。

        以下有三个问题:

        为什么可以同时返回ACK和SYN呢?下图可以解答这个疑问。

TCP的主要机制 | 网络编程_第3张图片       

        为什么不能两次握手?

        两次握手相当于没有了确认应答ACK。客户端向服务端发送SYN,建立连接。服务端收到后再向服务端发送SYN,建立连接。然后双方开始进行通信。那么这样就会产生很多问题,举一个例子,客户端发送了一个SYN后在网络中被阻塞了,等待了一会的客户端没有收到服务端的SYN所以就开始超时重传,第二个SYN的网络路径畅通,到达了服务端,服务端返回了一个SYN,双方开始通信。过了一会儿,先前被阻塞在网络中的SYN恢复畅通,到达了服务端,此时服务端收到后就会建立好连接并发送回SYN给客户端,等待通信。但是客户端并没有想要再次发起通信的想法,所以就对收到的SYN不理会。但是TCP服务端进程已经进入了已建立连接的状态,此时就会一直等待客户端发来数据,这就会白白浪费服务端主机的很多资源。

        如果采用的是三次握手,服务端发送完SYN后没有收到客户端的ACK就会知道客户端没有想要建立连接,此时就不会进入已建立连接的状态。

                2)四次挥手

        四次挥手是通信双方关闭连接的过程。

        FIN:结束报文段。一方向另一方发出结束通信的信息。客户端,服务端双方都能发出FIN,表示自己想终止通信了

                               TCP的主要机制 | 网络编程_第4张图片

         四次挥手跟三次握手很相似,多一个次数的原因在于接收方返回的ACK跟FIN没有合并到一次。在通常情况下,ACK与FIN并不会合并到一起,因为它们不是在同一个时机触发。ACK与SYN的发送是在内核完成的,所以接受方收到数据后内核立刻返回ACK,因为SYN也是由内核发送,所以就会顺带着(合并到一起)一起发送。而FIN是由应用程序代码控制的,在调用到socket的close方法的时候才会触发FIN。(代码用法可参考我的博客:TCP网络编程)

        接收方的代码运行到了close方法后,返回FIN,进程结束。需要注意的是,虽然进程结束了但双方的连接并不会立刻中断,因为TCP的连接是由系统内核维护的,所以只有到发送方的ACK被接收方收到了,此时才会断开连接(第四次挥手完成)。

        以下两个问题:

        1,为什么建立连接是三次握手,关闭连接是四次挥手?(另一种解释)

        假设客户端给服务端发送了FIN,这表示了客户端不再发送数据了。服务端接收到了客户端的FIN后立刻返回了ACK,但此时可能服务端还有数据没有发完,所以就会等到全部发送完毕后再发送FIN给客户端,客户端收到后再返回ACK,所以一共四次交互。

        2,发送方发送FIN后收到接收方返回的ACK跟FIN后,会发送一个ACK给接收方,但是发送完后不会立刻终止进程,而是进入了等待状态(等待2MSL),请问为什么还要等待2MSL?

        第一,客户端发送的ACK可能会丢失。假设丢失了,站在服务端的角度,我发送了ACK跟FIN后却没有收到ACK,此时就会进行超时重传。客户端在这个等待时间中收到了服务端的超时重传就会知道自己发送的ACK丢失了,此时就可以重新发送一个ACK并重置2MSL等待时间。

        第二,防止出现与三次握手相同的情况——发送的连接请求阻塞在网络中,一段时间后又到达了服务端。2MSL的等待时间,足以让本次通信过程中的所有报文段从网络中消失,这样新的连接就不会出现已失效的请求连接报文。

        4,滑动窗口

        TCP在保证了可靠性时,意味着损失了发送效率,如果每次只发送一个数据,那么每次都得等待对方返回一个ACK(确认应答),这样效率就太低了,TCP想要提高效率便选择了批量发送数据,这样就可以同一段时间等待多个ACK。如何批量发送数据,就通过滑动窗口来实现。

                           TCP的主要机制 | 网络编程_第5张图片

由上图可知每收到一个ACK就会立刻发送下一个数据,等待ACK的个数就称为窗口大小,如下图:

TCP的主要机制 | 网络编程_第6张图片

         在批量发送数据时,有可能会出现丢失的情况,下面来讨论这种情况:

情况一:服务器的ACK在返回时丢失

        这种情况是A的数据已经到达了B,但是B返回的ACK在途中丢失了

               TCP的主要机制 | 网络编程_第7张图片

        补充知识:如果没有使用滑动窗口机制,则每一次发送后都要等待确认应答,如果没有确认应答就会触发超时重传。而使用了滑动窗口机制,某些确认应答丢失了也无需重发。

        如上图即使前面四个确认应答中有三个丢失了,只要最后一个确认应答号6001有被A接收到,即表明1 ~ 6000 的数据B都接收到了,无需重发,可以开始发送从6001开始的数据了。

情况二:数据在发送时丢失

                             TCP的主要机制 | 网络编程_第8张图片

        观察上图,A向B批量发送1 ~ 7000 的数据,然后开始等待B的确认应答。B收到了1 ~ 1000 的数据,立刻返回了1001的确认应答号,表示向A索求1001之后的数据,但是1001 ~ 2000 的数据丢失了,没有到达B,此时B就会不断向A发送1001的确认应答号(ACK),A在收到1001的确认应答号后,又连续收到三次相同的确认应答,此时A就会判定1001 ~ 2000 的数据在发送过程中丢失了,并重传1001 ~ 2000 的数据。这种机制比之前的超时重传机制更加高效,因此也被称为快速重传。而此时B已经收到了1 ~ 1000  , 2001 ~ 7000的数据,在收到A重传的1001 ~ 2000的数据后,就会直接返回确认应答号7001,表示1 ~ 7000的数据我已全部收到,现在开始索要7001以后的数据。

        我们会发现B并没有发送 2001,3001,4001。。。。这些确认应答号而是直接就发送了7001,这个原因可以参考情况一,使用滑动窗口机制,只要最后的确认应答号有收到,处于中间的确认应答号丢失了也无需重发。

        滑动窗口的内容到这里就结束啦。

         看到这,可能有些人会有疑问,在情况一跟情况二中,为什么 B 不会返回中间的ACK而是直接返回最后一个数据的下一个确认应答号(比如上文收到缺失的数据后直接返回7001)

        原因很简单,在前面的内容中我们讲了TCP给每一个字节的数据都标记了专属的序列号,然后开始批量发送。而在网络传输中,转发的路径是多种多样的,序列号靠前的数据可能会因为阻塞导致比序列号靠后的数据更晚到达B,所以就会在 B 的接收缓冲区里进行重新排队。

TCP的主要机制 | 网络编程_第9张图片

         现在,排好队的数据向B中导入,通过编写的代码程序 socket里的InputStream方法 读取缓冲区的数据(代码用法可参考我的博客:TCP网络编程),读完就相当于清空了B的接收缓冲区,等待下一次批量发送的数据到达。

        5,流量控制

        在上一个机制里,我们提到了TCP通过滑动窗口机制来进行批量的发送数据。那么是否每次批量发送的数量越大越好呢?答案显然是否定的,TCP是在基于可靠性上提高效率的,发送的数量过大可能一下子就将接收方的接收缓冲区给塞满了,后面的数据就会被丢失,此时就不可靠了。

        所以,我们就要通过流量控制来限制一下发送方的发送速度。

        如何控制?   我们让ACK报文里,携带一个“窗口”字段。

        让我们重新来观察一下TCP报文

            TCP的主要机制 | 网络编程_第10张图片

        当ACK为1时(表示这个报文为ACK报文),窗口字段就会生效,这里的值就是接收方建议发送方发送的窗口大小值。

        这个值的获取非常简单粗暴,直接拿接收缓冲区里当前剩余空间的大小作为窗口的大小。

        我们再来看一下发送过程

        TCP的主要机制 | 网络编程_第11张图片

        在图中,右边的两列数字,左边表示的是确认应答号,右边表示的是建议窗口大小。当窗口值为0时,就表示此时接收缓冲区已满,主机A就会暂停发送数据。如果B的接收缓冲区腾出空间了就会向A发送一个窗口更新通知,然后A就会根据窗口值再次发送数据。

        窗口更新通知也有可能丢失,如果A过了超时重发的时间了还没有收到窗口更新通知,就会发送一个窗口探测报文(一个比较小的数据),如果返回的ACK中的窗口值仍为0就继续等待,非0则根据新的窗口值批量发送数据。

        在上述内容里,我们只是简单的把流量控制的窗口值就当做了滑动窗口的大小,实际的情况其实更为复杂,因为网络随时都可能阻塞,所以还得引入一个拥塞控制的概念。

        实际的滑动窗口大小 = (流量控制 / 拥塞控制)取较小值

        6,拥塞控制

        互联网是一块共享的空间,在主机A与主机B之间存在着许多的中间节点,比如各种路由器。

TCP的主要机制 | 网络编程_第12张图片    

        只要某个中间节点接收的数据超过了其传输能力上限,那么这个节点就会产生阻塞,进而导致整条路径发生阻塞。

        所以就要对网络中的数据传输进行拥塞控制。

                

拥塞控制的四个算法:1,慢启动

                                    2,拥塞避免

                                    3,快重传

                                    4,快恢复

        从左向右解读这张示意图。

        在一开始,如果突然就发送一个比较大的数据量,此时网络又恰好在拥堵中,那么就有可能导致整个网络的瘫痪,所以会选用慢开始算法对发送的数据量进行控制。

        要注意的是慢开始其实并不慢,它是以指数增长的。慢开始指的是拥塞窗口从1开始增长。

 第一次发送一个数据段,收到确认应答ACK后,第二次发送两个数据段,再次收到确认应答ACK后,第三次发送四个数据段,以此类推,拥塞窗口的大小以指数增长。

        在达到慢启动阀值()后,就改用拥塞避免算法。拥塞避免算法是将指数增长改为线性增长,每收到一次确认应答(ACK)下一次的窗口值大小就加一。如上图所示,窗口值增长到某一个数值后,触发了超时重传,此时就会判断为可能网络出现了阻塞,将窗口值置为1重新进行慢开始算法并将慢开始阀值(ssthresh)更新为发生阻塞时窗口值的一半

         之后的慢开始与拥塞避免过程与上面的类似,只是数值有所不同,此处不过多讲解。

        接下来来到快重传,在滑动窗口部分的情况二里我们讲到,如果某一个数据丢失了,接收方就会向发送方连续发三次相同的ACK,索要丢失的数据,这就是快重传的含义。发送方连续收到了三次相同的ACK就会知道是某个数据丢失了,而不是网络发生了阻塞,所以就不会采用慢开始算法,而是采用了快恢复算法。快恢复算法就是将慢开始阀值(ssthresh)和窗口值都变为当前窗口值的一半,并开始执行拥塞避免算法。

        以上就是拥塞控制的全部过程。在讲解中我并没有使用示意图中的数值,因为这些是假设的,在实际的应用中,我们要根据应用场景灵活调整各项数值,重点是要理解策略。

        7,延时应答

        延时应答的作用仍然是在保证TCP可靠性的基础上来提高传输效率。延时应答表示接收方在收到数据后并不会立刻就返回ACK,而是会磨蹭一会 ~ ~    这是因为在接收端刚接收完数据后,此时接收端的接收缓冲区是被占满的,如果立刻返回就ACK,那么返回的建议窗口值就会很小(忘记的同学复习一下上文流量控制的内容),影响了发送方下一批数据的发送(发送的数据量很少)。

        所以稍微磨蹭一会,让接收端多读取一些数据(应用程序读取完一批数据,就会将该数据从缓冲区里清空),这样等一下随ACK返回的建议窗口值就会更大一些,提高了下一次发送时的效率。

        这个设置的延时时间不会很久,否则就会导致发送方触发超时重传。

        8,捎带应答

        捎带应答机制是基于延时应答上面的,如果接收端收到数据后立刻就返回确认应答,那么就无法实现捎带应答。

        捎带应答的作用是减少通信双方发送信息的次数,

                                      TCP的主要机制 | 网络编程_第13张图片

        如图,本来接收端的 ACK 跟 apple 应该是分开发送的,但是由于前面提到的延时应答机制, apple 数据已经处理好准备发送时, ACK 依旧尚未发送,此时就可以将这两个报文整合为一个报文一起发送。显而易见,合并成一次发,比分开发送效率更高。

        9,黏包问题

        我们知道TCP的特性之一就是面向字节流,顾名思义,就是数据以字节为单位像水流一样源源不断的“流”过来。这些传输过来的数据在接收缓冲区里一个接着一个,紧紧的挨在一起,这样接收端就无法区分从哪里到哪里是完整的一个数据,此时就会很容易多读,或是少读了一部分数据。

               TCP的主要机制 | 网络编程_第14张图片

         上面举了一个生动的例子,对于B来讲,他还能区分出A的每一句话,因为A的每一句话都是以“小陈”开头的。而对于A来说,B回答的话简直是莫名其妙,毫不相干。这就是粘包问题。

        解决粘包问题有两个方法,第一个就是可以定义一个分隔符,就像上面的“小陈”一样。第二种方法就是约定长度,比如约定数据报的某个位置的数值,表示这个数据报的长度是多少,这样接收端收到数据后就知道该数据的长度了。

        10,异常处理

        1,进程关闭/进程崩溃

        进程关闭了,但是连接是系统内核在维护,此时连接仍然存在,仍然可以进行四次挥手,然后再停止连接。

        2,主机关机(正常流程关机)

        电脑杀死系统上所有正在进行中的进程。也会触发四次挥手,如果向对方发送FIN并等待对方返回FIN。如果四次挥手都挥完了那更好。

        如果没有,比如系统在收到对方返回的FIN后来不及发送ACK就关机了。此时对方没有收到ACK,就会触发超时重传,重传几次FIN后都没有收到ACK,就尝试重新连接,发现连不上了,就直接释放连接。

        3,主机断电(直接被拔了电源  /  停电)

        ①,对端是发送方:此时对端发送了数据后没接收到ACK ,触发超时重传,还没是没结果,重新连接,发现连接不上,放弃(释放)连接。

        ②,对端是接收方:此时对端没法知道,你这边是还没发送数据,还是已经挂掉了。

                                        此处TCP内置了一个心跳包机制,如果长时间没反馈,就说明已经挂掉了,此时接收方就会直接释放连接。

        4,网线断开

        与第3点相同的步骤。

        总结

        TCP为了实现可靠性,使用了确认应答机制和超时重传机制,这两个机制是TCP最核心的机制。基于确认应答机制,TCP在建立连接上就体现出了三次握手这样的形式,在释放连接上则是体现出了四次挥手。实现了可靠性,那就意味着牺牲了传输效率。为了提高效率,TCP采用了滑动窗口机制实现批量的发送数据,减少等待ACK的时间。而滑动窗口每次批量发送数据的大小则由流量控制和拥塞控制来共同决定。为了能再提高效率,TCP使用了延时应答。基于延时应答,就可以实现捎带应答,减少信息的发送次数,降低计算机的负荷。TCP面向字节流的特性引发了粘包问题,通过对异常的处理实现了心跳包的机制。

        

你可能感兴趣的:(多线程,网络,tcp/ip,java,网络协议)