TCP
TCP最核心的一点就是可靠的传输协议,那么TCP是靠什么来保证可靠的传输服务呢?
三次握手
就如同打电话一样
A:你能听到我的声音吗?
B:我能听到你的声音,你能听到我的声音吗?
A:我也能听到你的声音。
这样就能在有限的三次握手中确认双方都能够收到对方的消息。原本的握手是四次,不过把B的回答和问话优化为了一句。
具体的实现则是通过ack和syn这两个字段实现的:
- 第一步:client 发送 syn 到server 发起握手;
- 第二步:server 收到 syn后回复syn+ack给client;
- 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack;
四次挥手
为什么握手需要3次,挥手却要需要4次呢?
客户端和服务端在进行通信,客户端请求完了。
客户端:我这边已经搞定了,要关闭连接。
服务端:收到!同意你的关闭请求。
这时,客户端不能再发送数据,服务端也不能再接受数据了。但是服务端向客户端的传输可能没有结束,不能马上告诉客户端自己也完事了。不过会告诉客户端,我已经收到了。当过了一会服务端也搞定了则主动给客户端发送信息。
服务端:我这边也搞定了,需要关闭连接。
客户端:收到!
此时,服务端收到客户端的回答后关闭连接,之后客户端没有收到回复后,证明服务端已经关闭,就自动关闭连接
具体的实现通过fin和ack这两个字段实现的:
- 第一步: client主动发送fin包给server
- 第二步: server回复ack(对应第一步fin包的ack)给client,表示server知道client要断开了
- 第三步: server发送fin包给client,表示server也可以断开了
- 第四部: client回复ack给server,表示既然双发都发送fin包表示断开,那么就真的断开吧
超时重传
超时重传是TCP协议保证数据可靠性的一个重要机制,其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。三次握手,正常的数据传输,以及四次握手过程中只要出现超时情况,都会触发重传。
滑动窗口
为什么需要滑动窗口?
tcp以一个段为单位,每发送一段就进行一次确认应答处理。这样包的往返时间就会变长,通信性能就会变低。
A:1
B:收到1
A:2
B:收到2
......
为什么不能一次发送多个数据来提高性能呢?
A:1,、2、3、...
B:收到1、2、3、...
这就是窗口的由来,而无需等待确认就可以发送的数据的最大值就是窗口的大小。这个机制会大量用到缓冲区,通过对多个段同时进行确认。如果窗口内的部分数据出现了丢包,还是要进行重传,所以发送端需要这些数据包的缓存,直到收到了他们的确认应答。
收到确认的情况下,将窗口滑送到确认应答的序号的位置,这样可以顺序的讲多个段同时发送来提高性能,
考虑一种情况,就是发送成功了,但是返回的应答丢失了。首先按照以往的模式来的话,返回的应答丢失也要失败重传。有了窗口后,一定时间内不会马上重传,还是会发送数据。这样避免了重复的发送数据。
考虑另一种情况,有一段数据发送不成功。如果有一段数据发送不成功,其别的数据返回的时候不仅返回自己数据的应答,还会有未接受到的数据的确认应答。当窗口比较大的时候出现丢包的时候,会一直受到丢失的数据的确认应答,当这种情况出现3次后,缺失的数据将再次被进行重发。
拥塞控制
有了窗口机制大大提升了网络的传输效率。但是这样有一个缺点就是。网路是个共享的环境,经常因为其他主机之间的通信使得网络堵塞。如果再发送较多的数据,很有可能导致整个网络瘫痪。tcp的拥塞控制主要有4个算法:1. 慢启动;2. 拥塞避免;3. 快重传;4. 快恢复;1和2是拥塞发生前的算法,3是拥塞发生时,而4是拥塞发生后。
rwnd与cwnd
- rwnd是用于流量控制的窗口大小,主要取决于接收方的处理速度,由接收方通知发送方被动调整。
- cwnd是用于拥塞处理的窗口大小,取决于网络状况,由发送方探查网络主动调整。
慢启动算法
对于刚刚加入网络的链接,要一点点的提速,不要妄图一步到位。
连接刚建好,初始化cwnd = 1(当然,通常不会初始化为1,太小),表明可以传一个MSS大小的数据。
每收到一个ACK,cwnd++,线性增长。
每经过一个RTT,cwnd = cwnd * 2,指数增长(主要增长来源)。
还有一个ssthresh(slow start threshold),当cwnd >= ssthresh时,就会进入拥塞避免算法。
如果网速很快的话,ack返回快,那这个慢启动一点也不慢,将会以指数的速度增长。
拥塞避免算法
前面说过,当cwnd >= ssthresh(通常ssthresh = 65535)时,就会进入拥塞避免算法
(Congestion Avoidance):缓慢增长,小心翼翼的找到最优值
- 每收到一个Ack,cwnd = cwnd + 1/cwnd,显然,cwnd > 1时无增长。
- 每经过一个RTT,cwnd++,线性增长(主要增长来源)。
慢启动算法主要呈指数增长,粗犷型,速度快(“慢”是相对于一步到位而言的);而拥塞避免算法主要呈线性增长,精细型,速度慢,但更容易在不导致拥塞的情况下,找到网络环境的cwnd最优值。
快重传
慢启动与拥塞避免算法作用在拥塞发生前,采取不同的策略增大cwnd;如果已经发生拥塞,则需要采取策略减小cwnd。那么,TCP如何判断当前网络拥塞了呢?很简单,如果发送方发现有Seq发送失败(表现为“丢包”),就认为网络拥塞了。
丢包后,有两种重传方式,对应不同的网络情况,也就对应着两种拥塞发生时的控制算法:
- 超时重传。TCP认为这种情况太糟糕,调整力度比较大:
- ssthresh = cwnd /2
- cwnd = 1,重新进入慢启动过程(网络糟糕,要慢慢调整)
- 快速重传。TCP认为这种情况通常比RTO超时好一些,主流实现TCP Reno的调整力度更柔和(TCP Tahoe的实现和RTO超时一样暴躁):
- ssthresh = cwnd /2
- cwnd = cwnd /2,进入快速恢复算法(网络没那么糟,可以快速调整,见下)
可以看到,不管是哪种重传方式,ssthresh都会变成cwnd的一半,仍然是指数回退,待拥塞消失后再逐渐增长回到新的最优值,总体上在最优值(动态)附近震荡。
回退后,根据不同的网络情况,可以选择不同的恢复算法。慢启动已经介绍过了,下面介绍快速恢复算法。
快速恢复
如果触发了快速重传,即发送方收到至少3次相同的Ack,那么TCP认为网络情况不那么糟,也就没必要提心吊胆的,可以适当大胆的恢复。为此设计快速恢复算法
(Fast Recovery),下面介绍TCP Reno中的实现。
回顾一下,进入快速恢复之前,cwnd和sshthresh已被更新:
- ssthresh = cwnd /2
- cwnd = cwnd /2
然后,进入快速恢复算法:
- cwnd = ssthresh + 3 * MSS (尝试一步到位)
- 重传重复Ack对应的Seq
- 如果再收到该重复Ack,则cwnd++,线性增长(缓慢调整)
- 如果收到了新Ack,则cwnd = ssthresh ,然后就进入了拥塞避免的算法了