TCP,即Transmission Control Protocol,传输控制协议。
TCP对数据传输提供的管控机制,主要体现在两个方面:可靠传输和效率,可靠传输是TCP 的初心。这些机制和多线程的设计原则类似:保证数据传输可靠的前提下,尽可能的提高传输效率。下面会介绍一些TCP协议的机制,帮助你理解TCP协议(传输层)。
确认应答机制是保证TCP可靠传输的核心机制,TCP将每个字节的数据进行编号,每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发送。
确认序号就是TCP报文中的32位确认序号,这个序号以可以确保应答报文和数据对应,在出现“后发先至”这样的情况时,程序可以按照正确的顺序组织数据。
可靠传输,并不是指数据能够百分百到达对端,因为现实网络环境是非常复杂的,受到各种因素的综合制约。这里的可靠传输主要有两方面含义:
如何区分一个TCP数据报是普通报文还是应答报文呢?TCP报文中ACK这一位为1,表示当前数据时一个应答报文,此时该数据报中的32位确认序号字段就会生效,反之同理。
TCP可靠传输是通过以确认应答为核心,加以其他机制(超时重传,连接管理等)共同完成的。
确认应答是在,应答报文一定能被收到的理想情况下,但是应答报文也可能会丢包。那么为什么会产生“丢包”呢?
我们发送的数据在发送途中经过路由器、交换机的转发,但是交换机的处理能力是有限的,如果同一时间的流量过大,路由器、交换机不会积压这些数据,而会之久丢弃一部分,只在自己的承受范围内处理数据,而被丢弃的数据也就永远的消失在了网络上了。
如果应答报文(ACK)丢包了,那么发送方也就无法得知是否发送成功,此时可靠传输也就无从谈起了。引入“超时重传”机制,就可以在应答报文或者普通报文发出后,一段时间内无响应后,选择进行重新传输。
超时重传的丢包有数据报丢包、应答报文丢包两种情况,而发送方根本无法区分这两种情况,会“一视同仁”的进行重传操作。由于重传后到达对端的概率会大大增加,如果重传多次,还未收到对端ACK,说明网络上已经出现了严重故障。TCP对重传成功到达对端这件事是抱有“悲观预测”的,超时的等待时间也会逐渐变长,当传输一定次数后还没有收到对端的ACK,此时就会触发TCP的重置连接操作。
我们之前说过TCP协议是有连接的,这个连接的建立过程,就是三次挥手(打招呼),断开连接就是四次挥手。
这里“挥手” handshake,类似于打招呼,发起连接的一方向对方,发送不包含业务数据的数据报,用来唤起对方的注意,从而触发后续的操作。
1. 客户端主动发起建立连接的请求,向服务器发送一个不携带业务数据的数据报(syn 同步报文段)
2. 服务器接收到客户端的建立连接请求后,会返回一个应答报文(ack),并且也会向客户端发送一个syn,并且这两个报文是合并为一个报文发送的(报文中SYN 和 ACK 都为1)【此时服务器验证了自己的接收能力】
3. 客户端收到服务器发来的应答报文和同步报文段也会向服务器返回一个ack【客户端验证了自己的接收能力和发送能力】
4. 服务器收到客户端发来的ack,连接建立完成。
TCP的初心还是可报传输,如果此时网络已经存在重大故障,可靠传输也就无从谈起了。
三次握手核心作用
四次握手中的ACK和FIN一般不能合并,ACK和FIN的触发时机不同,ACK在内核收到数据后会立即进行响应,而FIN要在应用程序中调用close()方法后才能触发。这两个操作一般是不会合并的,但是在延时应答机制和捎带应答机制下,也可能合并。
这里只介绍了,四次挥手完成,正常的连接断开,不分异常断开的情况会在下文的异常处理中介绍到。
由于丢包状况的存在,前三次挥手若丢包都能正确触发重传,但是如果最后一次挥手丢包了,而客户端又早早地进入CLOSED状态(连接断开),那么服务器就会等待ACK报文,又因为客户端以及断开连接不会触发超时重传,所以服务器永远也等待不到客户端的ACK了。
在这种情况下,客户端子啊第三次挥手之后不会立即进入CLOSED状态,而是进入TIME_WAIT状态,等待一段时间,若ACK丢了,就会触发服务器的超时重传,重发FIN,进而客户端再次进行ACK响应。
这里的TIME_WAIT的等待时间一般会设为2*MSL,MSL是网络上两个节点的最大消耗时间,确保挥手留有客户端ACK到达服务器和ACK丢包服务器超时重传的时间。
前面介绍的确认应答、超时重传、连接管理机制都是为了保证可靠传输,而可靠传输必定要牺牲一定的效率,而滑动窗口机制则是一种”亡羊补牢“的措施。让TCP的效率尽可能的高一点。
在原来的机制下,没发送一个TCP数据报都要等待对端返回ACK后才能继续发送,而滑动窗口机制则是先发送一批数据,对这一批数据进行集中等待ACK,收到某个ACK窗口就可以往下滑动。在不等待的情况下,批量发送多少数据的上限成为”窗口大小“。
在这个过程中如果产生丢包,有两种情况。
一是ACK丢了,这种情况是不需要进行重传的,因为ACK在滑动窗口机制下被赋予了新的含义,是从xxx个字节之前的数据都收到了,如果在这之前的ACK丢失并不会影响到现在的判断。
二是数据丢了,数据丢失后,接受方会将这个之后的数据丢弃,不断向发送方索要该序号的数据,等待该数据报的到来。
何时回触发TCP的滑动窗口机制呢?如果收发双方传输的数据量比较小,也不平频繁,此时不会触发滑动窗口机制,任然是按照确认应答和超时重传机制传输。但是,如果收发双方传输的数据量较大,比较频繁,就会触发滑动窗口机制,进行批量发送,快速重传。
通过滑动窗口机制传输数据,效率得到了一定的提升,但是这个窗口也不是越大越好,为了控制窗口大小,引入了流量控制机制和拥塞控制机制。分别从接收方的处理能力和网络路线的状况来优化窗口大小,动态调整。
由于接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。TCP支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控(Flow Control) - 生产者消费者模型。
流量控制机制是站在接收方的角度,反向制约发送方的发送速度,发送方的发送速度,不应该超过接收方的处理能力。发送方会根据接受方缓冲区的剩余空间大小,来作为衡量窗口大小的指标,这就意味着:
TCP报文中的16位窗口大小,就是用于接收方将自己当前的缓冲区大小反馈给发送方。这个窗口大小只有16位,这是否以为这最大只能是64kb呢?TCP报文中的选项中,有一部分叫做窗口扩展因子,如果当前传输情况理想,窗口是可以更大的。
在这个过程中如果接收方的接收缓冲区满了,那么发送方就会停止发送,等待接收方对接收缓冲区内的数据进行处理,那么停止多久呢?此时发送方会周期性的发送一个不携带业务数据的”窗口探测包“,这个数据报知识为了得到接收方的ACK,从而判断接收方的接收缓冲区的大小,从而做出进一步判断。
流量控制机制是站在接收方的数据处理能力视角上对窗口大小进行优化,这里的拥塞控制则是站在网络传输路径是否畅通(通信中间节点的情况)对窗口大小进行优化,最终的窗口大小要取两者的较小值。
由于木桶效应,发送速度收到整个通信状况中的最短板决定,所以这里的拥塞控制会在不大于接收方的处理能力的情况下,逐渐增大 ,增大到某个临界点,出现丢包时再将窗口大小进行调整,此处也是动态变化的。
如果在刚开始阶段就发送大量的数据,可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。TCP拥塞控制是一种慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;我们先看一个经典的拥塞控制。
刚开始从一个比较小的速度开始发送,指数级递增,当达到某个阈值时停止指数增长,改为线性增长(假发增大),如果出现拥塞,那么直接从速度归零重新进行“慢开始”。
这种经典的拥塞控制一旦遭遇拥塞,那么就会重新慢开始,下降速度太快,有一种开进版的拥塞控制,遭遇拥塞后,在原有阈值的基础上,计算出新的阈值,从这个新的阈值开始线性增长。
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
在保证网络不会拥塞的情况下,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。
当收到发送方的数据时,不必立即反馈ACK,等待接收方进行数据处理,让缓冲区空间剩余大小进行增长,此时窗口就会尽可能的大。
捎带应答实在延时应答的基础上,进一步做出的效率上的优化,延时应答后,将应答报文和数据报文进行合并,一次性发送给发送方。减少了中间的封装和分用的开销。在这种情况下,TCP断开连接的四次挥手也是可能三次挥完的。
由于TCP是面向字节流的,在接收方接收到数据从缓冲区读取数据时,就无法区分应用层数据报之间的边界。如果同时到来多个,那么就可能出现粘包问题。
相比较之下,UDP则是面向数据报的,UDP的接收缓冲区中是一个个的DatagramPacket对象,应用程序读取时可以明确的知道边界,就不会出现粘包问题。
要解决TCP的粘包问题,我们只需要明确TCP->应用层数据包间的边界。
此处的应用层协议可以自定义,如xml、json、protobuffer等(边界明确的),当然也可以自定义应用层协议。
进程没了,异常终止了。进场的文件描述符表也就释放了。相当于调用socket.close(),此时就会触发FIN,对方收到之后,会返回FIN和ACK,这边再进行ack (正常的四次挥手断开连接的流程)tcp的连接,可以独立于进程存在。(进程没了,tcp连接还在)
在进行关机操作时,会先触发强制关闭进程,相当于进程崩溃。此时就会触发FIN,对方收到就就会返回 ACK和FIN,但不仅仅是进程结束了,操作系统也关闭了。
主机掉电是一瞬间的事情,对端是来不及反应的。
假设A给B发送数据,一旦网线断开,A就会触发超时重传 => 重置连接 => 断开连接。B就会 触发心跳包 => 对端未响应 => 断开连接【TCP的心跳包周期较长】
TCP 的优势在于可靠传输,TCP可以适用于绝大部分场景。
UDP的优势在于效率跟高,适用于对”可靠性不敏感,效率敏感“的场景,同一个机房内部的数据传输就会优先考虑UDP,因为同一个机房内丢包的概率不大,希望数据能够更快的传输。在需要广播的场景下UDP也有得天独厚的优势,UDP天然支持广播,所谓广播就是将数据发给当前局域网的所有设备。
如果本篇文章对你有帮助,请点赞、评论、转发,你的支持是我创作的动力。