TCP 是一种面向连接的可靠传输协议,那它是怎么保证数据传输的完整性和可靠性的呢?
主要原因是 TCP 的以下几种机制:确认应答、超时重传、流量控制、拥塞窗口。
1、确认应答机制
在 TCP 传输的过程中,TCP 将每个字节的数据都进行了编号,即为序列号,如 1,2,3 字节....
比如:当客户端给服务端发送了 1~1000 字节的数据时,服务端如果收到了,就会给客户端应答(ACK报文段,每一个ACK都带有对应的确认序列号),表示客户端发给过来的的 1~1000字节 的数据我已经全部收到了(收到哪些数据),下次只需要从1001数据开始发(下次从哪里开始发)。客户端收到应答之后就知道对方已经收到了 1~1000 的全部数据,所以再一次发送数据的时候他就会从1001开始发,后面都是依此类推的情况。
当然了,如果客户端给服务端发送了数据之后,经过一端时间客户端并没有收到服务端的应答,这个时候为了确保数据的准确到达,TCP 就有了超时重传机制。
2、超时重传机制
导致超时重传机制的方式有两种:
客户端数据包根本就没有传送到服务端,因此服务器就不会回传一个确认应答的报文。
服务端收到了数据包,也回传了确认应答报文,但是该报文丢失了。
第一种情况:客户端给服务端发送了数据,可能因为其他的原因数据无法到达服务端(比如网络拥堵),这个时候客户端在等待一个 特定的时间间隔 之后,发现服务端还没有确认应答,于是就再一次将上一次的数据重新发送过去。
第二种情况:服务器收到了客户端发来的数据,但是服务端返回给客户端的确认应答并没有准时到达,这时客户端也会因为没有收到确认应答而再次重新将数据发送过去。
问题1:服务端就会收到很多的重复数据。如上第二种场景,实际情况是服务端第一次就已经收到了客户端发来的数据,但客户端依旧会重发数据,已确服务端已经收到数据,从而进行下一次的数据转发。可想而知服务端就会收到很多的重复数据,但是重复的数据显然是不需要的,那么 TCP 协议就需要能够识别那些重复的数据并且要将冲符的数据丢弃掉。这个时候 序列号 就发挥他的一项作用了——去重。每一个数据都有自己的序列号,如果服务端收到重复的数据,那么必然就会产生多个序列号相同的数据,那么序列号相同的数据就必然是重复的数据,去掉即可。
问题二:特定的时间间隔。最理想的情况是,找到一个最小的时间保证确认应答一定能在这时间内返回,但是这个时间的长短,随着网络环境的不同,也是有差异的。如果超时时间设得太长,会影响整体的重传效率。如果超时时间设的太短,有可能多次的发送重复的数据包。
TCP 为了应对无论在何种环境下都能比较高性能的通信,会动态计算这个最大超时时间:Linux 中,超时以 500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍。如果重发一次,任然得不到应答,就等待 2*500ms 后在进行重传,如果还得不到应答,等待 4*500ms 再重传,依次类推,以指数形式递增。累积到一定的重传次数,TCP 认为网络或者对端主机出现异常,就会强制关闭连接。
3、流量控制机制
TCP 协议在传输的过程中,以接收端处理数据的速度是优先的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送就会造成丢包,继而引起丢包重传等一系列连锁反应。因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制。
接收端将自己可以接受的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段,通过 ACK 段通知发送端;窗口大小字段越大,说明网络的吞吐量越高,接收端一旦发现自己的缓冲区快满了就会将窗口大小设置成一个更小的值通知发送端,发送端接收到窗口大小以后,就会减慢自己的发送速度。如果接收缓冲区满了,就会将窗口置为 0,这时发送方不再发送数据。但是需要定期的发送一个试探窗口,目的是为了取探测数据段,是接收端把窗口大小告诉发送端。
注:TCP 报文中的窗口大小是指:TCP 的首部中有一个 16 位的窗口大小的字段,就是存放的窗口大小的信息,另外在 TCP 的首部中的 40 字节选项中还包含了一个窗口扩大因子 M,实际的窗口大小是窗口字段的值左移 M 位。
4、拥塞窗口机制
TCP 中存在一个可以提高性能的滑动窗口机制,滑动窗口能够高效可靠的发送大量的数据,但是如果在一开始就发送大量的数据任然可能引发问题。要知道在网络上有很多的计算机,有可能当前的网络状态已经很拥堵,在不清楚当前的网络状态下,贸然发送大量的数据,这样对于已经很拥堵的网络来说无疑是雪上加霜。由此,TCP引入了 慢启动机制:先发送少量的数据,由此取探测当前的网络的拥堵状态,在决定按照多大的速度传输数据。
拥塞窗口:发送开始的时候定义拥塞窗口大小为 1,每当收到一个 ACK 应答以后拥塞窗口加 1,每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小值作为实际发送的窗口。拥塞窗口的增长速度呈指数形式增长,我们提到说慢启动,只是说初始的时候慢而已,但是增长速度非常的快。为了增长的不那么快,因此我们不能让拥塞窗口单纯的加倍,这里引入一个慢启动的阈值,当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是改为按照线性的方式增长。当 TCP 开始启动的时候,慢启动阈值等于窗口最大值,在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置为 1。
少量的丢包,仅仅只会触发超时重传,而大量的丢包就认为是网络拥堵,当 TCP 通信开始后,网络吞吐量会逐渐的上升,随着网络发生拥堵,吞吐量会立刻下降,拥塞控制说到底就是 TCP 协议想尽可能快的将数据传输给对方,但又要避免给网络造成太大的压力的折中解决办法。