TCP那些事
本文是《TCP-IP详解.卷1 协议》的读书笔记
1 TCP简介
TCP提供一种可靠的、面向连接的字节流服务。TCP通过下面的方式来保证服务是可靠的:
- 应用程序被分隔成TCP认为最适合发送的数据块,数据报的长度将保持不变(也就是说每次传送的报文段的大小是一样的);
- 当TCP发出一个报文段后,就会启动一个定时器,等待对方确认收到这个报文段,如果在这个时间内没有收到确认,则会重传这个报文段;
- 当TCP收到另一端的数据后,会发送一个确认;
- TCP保持首部和数据的检验和,这是端到端的检验和,如果收到数据的检验和有错,则不确认这个报文段(等待重传);T
- CP报文段是封装为IP数据报来发送的,因此到达时可能会失序,因此TCP会提供重新排序的功能,将正确的数据交给应用层。
- TCP会丢弃重复的报文段;
- TCP还提供流量控制,连接的双方都有一个缓冲区,另一端只允许发送接收端缓冲区所能接受的大小的数据;
对于每个连接,TCP会维护四个不同的定时器:
- 1)重传定时器:定义希望收到另一端的确认的最长时间;
- 2)坚持定时器(persist):使窗口大小信息 保持不断流动;
- 3)保活定时器:用来检测连接的另一端什么时候崩溃或重启了;
- 4)2MSL定时器:用来测量一个连接处于TIME_WAIT状态的时间;
1.1TCP首部
TCP的数据会被封装在一个IP数据报中,如下图:
TCP的首部如下图:
2 TCP连接的建立与终止
2.1 TCP连接的建立
建立一条TCP连接过程如下:
1)请求端(客户端)发送一个SYN段,指明客户打算连接的服务器的端口号,以及初始序号(ISN,在这个例子中为:1415531521)。这个SYN段为报文段1;
2)服务器发回包含服务器初始序号的SYN报文段(报文段2)作为应答。同时将确认序号设置为客户的ISN加1以对客户的SYN进行确认。一个SYN占用一个序号;
3)客户端必须将确认序号设置为服务器的ISN加1对服务器的SYN报文段进行确认(报文段3);
这三条报文完成连接的建立,建立与终止的过程如下图:
报文段1,2,3完成TCP连接的建立,报文段4,5,6,7完成连接的终止过程(中间省略了一些数据传送的报文),接下来会介绍终止的具体过程。
2.2 TCP连接的终止
终止一个TCP连接需要4次握手,也就是4个报文段,这是由于TCP是全双工的(每个方向都可以传送数据)导致的,因此连接的每一端都需要单独地进行关闭;当一方完成数据发送后就可以发送一个FIN来告诉另一方自己的数据已经发完了,而另一方在收到FIN后仍然可以发送数据。连接终止的过程如下:
1)客户端发送一个FIN用来关闭从客户端到服务器的数据的传送(报文段4);
2)服务器在收到FIN后,发回一个ACK,确认序号为收到的序号加1(报文段5)。同时还会向客户端发送一个文件结束符FIN来关闭连接(报文段6);
3)客户端在收到服务器的FIN后,必须发回一个确认(确认序号为收到的序号加1)(报文段7);
当TCP处于连接或终止的不同状态时,状态也是不一样的,TCP的状态变迁图为:
2.3 2MSL等待状态
TIME_WAIT状态也叫2MSL等待状态,每个TCP的实现都必须选择一个报文段的最大生存时间MSL(Maximum Segment LifeTime),这是每个报文段在网络内存活的最长时间。对于给定的MSL值,当TCP执行一个主动关闭时,并发回最后一个一个ACK时,该连接在TIME_WAIT状态停留的时间为2倍的MSL,这样在最后的ACK丢失后可以让TCP再次重发(另一端超时并重发最后的FIN)。在连接处于2 MSL等待时,任何迟到的报文段将被丢弃
无论何时关闭一个连接,一端必须保持这个连接,我们看到TIME_WAIT状态将处理这个问题。处理的原则是执行主动打开的一端在进入这个状态时要保持的时间为TCP实现中规定的MSL值的两倍。
2.4 坚持定时器
TCP通过让接收方指定希望从发送方接收的数据的字节数来进行流量控制,如果窗口为0,则会有效阻止发送方发送数据,这里就需要设置TCP的坚持定时器,不断地探查已关闭的窗口,直到窗口被打开或者应用进程使用的连接被终止;
3TCP的超时与重传
TCP提供可靠的运输层服务,它使用的方法就是确认另一端传来的数据,但数据和确认都可能丢失,因此TCP通过在发送时设置一个定时器来解决这个问题。只要当定时器溢出时还没有收到确认,则就重传该数据。实现这个方法的关键就是超时与重传的策略。
3.1往返时间的测量
TCP超时与重传中最重要的一个部分就是对一个给定连接的往返时间的测量。因为路由器与网络流量是会变化的,TCP应该根据这些变化来定义其超时的时间;
首先TCP会测量发出一个报文段和接收到该报文段的确认之间的RTT(Round-TripTime,往返时延),最初的TCP规范使TCP使用低通过滤器来更新一个被平滑的RT T估计器(记为O):R← Rx+ ( 1- x)M(M是新测量得到的值,x是一个平滑因子,推荐为0.9,也就是说每次进行新测量时,原来的时间估计占用09%,新的测量的值占10%)。在使用这个算法的前提下,RFC 793推荐的重传超时时间RTO(Retransmission TimeOut)的值应该设置为:RTO = Ry ,这里的y是一个推荐值为2的时延离散因子。
但在RTT变化范围很大时,就可能会跟不上这种变化,导致不必须的重传,而不必须的重传可能会增加网络的负载。因此还需要跟踪RTT的方差,在往返时间变化很大时,根据均值和方差来计算RTO 。每个RTT测量M的公式。
Err = M-A
A←A + gE rr
D←D + h(|E rr|-D)
RTO = A + 4D
这里的A是被平滑的RT T(均值的估计器)而D则是被平滑的均值偏差。E rr是刚得到的测量结果与当前的RT T估计器差。A和D均被用于计算下一个重传时间( RTO)。增量g起平均作用,取为1 / 8(0 .125)。偏差的增益是h,取值为0.25。当RTT变化时,较大的偏差增益将使RTO快速上升。
3.2 慢启动与拥塞避免
慢启动算法是一个在连接上发起数据流的方法,一开始只允许发送一个报文段,当这个报文段被确认后就可以再发送2个报文段,当这两个报文段被确认后就可以发送4个报文段。(也就是说每收到一个报文段的确认后,可以发送的报文段的大小就+1)。拥塞避免算法是用来处理报文段丢失的方法,报文段丢失有两种情况:超时或接收到重复的确认。 一般在实现中,这两种算法是一起实现来使用的。
拥塞避免算法和慢启动算法需要对每个连接维持两个变量:拥塞窗口(cwnd,congestion window)和慢启动门限(ssthresh,slow start threshold),算法的工作过程如下:
1)对一个连接,初始化cwnd为1个报文段,ssthresh为65535字节;
2)TCP发送的报文段的大小不能超过cwnd和接收方通告窗口的大小,拥塞窗口是发送方使用的流量控制(发送方对网络新状况的估计),而通告窗口则是接收方进行的流量控制(与接收方的缓存大小有关);
3)当拥塞发生时(超时或收到重复的确认),ssthresh被设置为当前窗口的一半(cwnd和通告窗口的最小值,但最少为2个报文段),如果是超时引起的拥塞,则cwnd会被设置为1个报文段(慢启动);
4)当新的数据被确认时,就增加cwnd(当cwnd<ssthresh时,进行慢启动,否则执行拥塞避免算法);慢启动算法的cwnd初始为1,收到一个确认cwnd 就+1,直到cwnd=ssthresh时,后面就进行拥塞避免算法了,拥塞避免算法每次收到确认时,将cwnd 增加1/cwnd;
3.3 快速重传与快速恢复
当我们接收到重复的ACK时,我们不知道是发生的报文段的丢失还是仅仅发生了报文段的重新排序,如果只是收到1到2个重复的ACK,则可能是发生了报文段的重新排序。但如果收到3个以上的重复ACK,就可能是发生了报文段的丢失,于是我们就重传丢失的报文段数据,而不用等待超时定时器的溢出,这就是快速重传算法,接下来执行的是拥塞避免算法而不是慢启动算法,这就是快速恢复算法。这个算法的实现过程如下:
1)当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd的一半。重传丢失的报文段,并将当前拥塞窗口cwnd设置为 ssthresh窗口加上3个报文段大小 ;
2)每次收到另一个重复的ACK时,cwnd增加一个报文段大小,并发送一个分组(如果新的cwnd允许发送);
3)当下一个确认新数据的ACK到达时,设置cwnd为ssthresh的值(恢复到拥塞发生前的水平);
推荐阅读耗子叔的:《TCP的那些事儿(上)》,《TCP的那些事儿(下)》