网络:TCP维护安全可靠机制提供的定时器

一、TCP为维护安全可靠机制提供了七大定时器

 

1、连接建立(connectionestablishment)”定时器

       在发送SYN报文段建立一条新连接时启动。如果在75秒内没有收到响应,连接建立将中止。

 

2、重传(retransmission)定时器

       在TCP发送某个数据段时设定。如果该定时器超时而对端的确认还未到达,TCP将重传该数据段。重传定时器的值 (即TCP等待对端确认的时间)是动态计算的,与RTT的估计值密切相关,且还取决于该报文段已被重传的次数。

 

3、延迟ACK(delayedACK)定时器

       在TCP收到必须被确认但无需马上发出确认的数据时设定。如果在200ms内,有数据要在该连接上发送,延迟的ACK响应就可随着数据一起发送回对端,称为捎带确认。如果200ms后,该确认未能被捎带出去,则定时器超时,此时需要发送一个立即确认。

 

4、持续(persist)定时器(零窗口探测定时器(PT0))

       在连接对端通告接收窗口为0,阻止TCP继续发送数据时设定。由于连接对端发送的窗口通告不可靠(只有数据才会被确认,ACK不会被确认),允许TCP继续发送数据的后续窗口更新有可能丢失。因此,如果TCP有数据要发送,但对端通告接收窗口为0,则持续定时器启动,超时后向对端发送 1字节的数据,判定对端接收窗口是否已打开。

 

5、保活(keepalive)定时器

       在TCP控制块的so_options 字段设置了SOF_KEEPALIVE选项时生效。如果连接的连续空闲时间超过2小时,则保活定时器超时,此时应向对端发送连接探测报文段,强迫对端响应。如果收到了期待的响应, TCP可确定对端主机工作正常,在该连接再次空闲超过 2小时之前,TCP不会再进行保活测试。如果收到的是RST复位响应, TCP可确定对端主机已重启。如果连续若干次保活测试都未收到响应, TCP就假定对端主机已崩溃,但它无法区分是主机故障还是连接故障。

 

6、FIN_WAIT_2定时器

       当某个连接从FIN_WAIT_1状态变迁到FIN_WAIT_2状态并且不能再接收任何新数据时,FIN_WAIT_2定时器启动,设为10分钟。定时器超时后,重新设为75秒,第二次超时后连接被关闭。加入这个定时器的目的是为了避免如果对端一直不发送 FIN,某个连接会永远滞留在FIN _ WAIT_ 2状态(假设TCP不选用半打开功能)。

 

7、TIME_WAIT定时器(2MSL定时器)

       2MSL指两倍的MSL,即最大报文段生存时间。当连接转移到TIME_WAIT状态,即连接主动关闭时,定时器启动。状态转换图那一节中已经详细说明了需要2MSL等待状态的原因。连接进入TIME_WAIT状态时,定时器设定为1分钟,超时后,TCP控制块被删除,端口号可重新使用。

 

8、SYN|ACK定时器

       TCP服务器在收到SYN请求后发送SYN|ACK响应,TCP在发送SYN|ACK响应后设置SYN-ACK定时器,然后等待对端的ACK到来以完成三次握手。如果没有收到ACK,TCP应该重传SYN|ACK,这个功能由SYN-ACK定时器完成。由于SYN|ACK发送后并没有放入发送队列中,故重传时必须重新构建SYN|ACK报文。 

 

9、ER延时定时器

       TCP发送的数据如果丢失则快速重传算法会立即重传数据而不用等到重传定时器超时,从而快速地恢复数据。如果发送端接收不到足够数量(一般来说是3个)的ACK,则快重传算法无法起作用,这个时候就只能等待RTO超时。ER算法主要是为了解决这个问题。

       在下面的条件下,就会导致收不到足够的ACK:

       (1)拥塞窗口比较小

       (2)窗口中一个很大数量的段丢失或者在传输的结尾处发生了丢包

         如果满足了上面的两个条件,那么就会发生发送端由于接收不到足够数量的ACK导致ER可以基于两种模式,一种是基于字节的,一种是基于段(segment-based)的,Linux中的ER是基于段的。ER算法会在小窗口下(flight count 小于4)减小触发快重传的重复ACK的阈值,比如减小到1或者2。而在Linux的实现中为了防止假超时会加上一个延迟再重传数据,这个功能就靠ER定时器实现。快重传算法无法生效.

 

       在这些条件满足后,ER定时器会被设置,其超时时间是一个比重传定时器更小的值。

        安装丢失探测定时器、重传定时器、坚持定时器时ER定时器就会被清除。

      

       (1)有丢包事件发生

       (2)重复的ACK小于等于乱序的阈值

       (3)未开启FACK,或没有未收到确认的包,或队列首包已发送但未超时

       (4)已发送的数据 > 乱序的阈值,或被SACK段的数量小于阈值,或允许发送skb

(5)thin_dupack功能未开启,或当前链接并不是"thin"的,或重复ACK的数量大于1,或SACK未开启,或有要发送的数据

       (6)do_early_retrans开启

       (7)没有重传完毕但没有确认的报文

       (8)有被SACK的报文

       (9)在网络中的报文数量比被SACK的报文数量多至少1个

       (10)在网络中的报文数量少于4

       (11)现在不允许发送数据

       (12)sysctl_tcp_early_retrans的值是2或3

       (13)ACK中没有ECE标记

       (14)tp->srtt(smoothed round trip time)的值大于0

       (15)icsk->icsk_retransmit_timer超时时间在延迟时间之后

            值得注意的是在快速重传时不会重传已经被SACK过或被重传过的skb,这些skb也许能够顺利收到,在这里不重传会减小网络拥塞。

 

 

虽然定义了9个定时器,但是内核中只用了4个实例(timer_list),所以有些定时器是共用一个实例的。

这4个实例分别是:

icsk->icsk_retransmit_timer:超时重传定时器、持续定时器、ER延迟定时器、PTO定时器。

icsk->icsk_delack_timer:ACK延迟定时器。

sk->sk_timer:保活定时器,SYNACK定时器,FIN_WAIT2定时器。

death_row->tw_timer:TIME_WAIT定时器

 

 

二、TCP要保证正常工作,至少应该有四种计时器:重传计时器、持久计时器、保活计时器和时间等待计时器。

 

1、    重传计时器:

 

超时重传是TCP协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。当TCP发送报文段时,就创建该特定报文段的重传计时器。

可能发生两种情况:

 1) 若在计时器截止时间到之前收到了对此特定报文段的确认,则撤销此计时器。

 2)收到了对此特定报文段的确认之前计时器截止期到,则重传此报文段,并将计时器复位。

 

1.重传超时时间:

 

   重传机制协议效率的一个关键参数是重传超时时间(RTO,Retransmission TimeOut)。RTO的值被设置过大过小都会对协议造成不利影响。如果RTO设置过大将会使发送端经过较长时间的等待才能发现报文段丢失,降低了连接数据传输的吞吐量;另一方面,若RTO过小,发送端尽管可以很快地检测出报文段的丢失,但也可能将一些延迟大的报文段误认为是丢失,造成不必要的重传,浪费了网络资源。

如果底层网络的传输特性是可预知的,那么重传机制的设计相对简单得多,可根据底层网络的传输时延的特性选择一个合适的RTO,使协议的性能得到优化。但是TCP的底层网络环境是一个完全异构的互联结构。在实现端到端的通信时,不同端点之间传输通路的性能可能存在着巨大的差异,而且同一个TCP连接在不同的时间段上,也会由于不同的网络状态具有不同的传输时延。

因次,TCP协议必须适应两个方面的时延差异:一个是达到不同目的端的时延的差异,另一个是统一连接上的传输时延随业务量负载的变化而出现的差异。为了处理这种底层网络传输特性的差异性和变化性,TCP的重传机制相对于其他协议显然也将更为复杂,其复杂性主要表现在对超时时间间隔的处理上。为此,TCP协议使用自适应算法(AdaptiveRetransmissionAlgorithm)以适应互联网分组传输时延的变化。这种算法的基本要点是TCP监视每个连接的性能(即传输时延),由此每一个TCP连接推算出合适的RTO值,当连接时延性能变化时,TCP也能够相应地自动修改RTO的设定,以适应这种网络的变化。

  

2.连接往返时间:

 

  个连接而言,若能够了解端点间的传输往返时间(RTT,Round Trip Time),则可根据RTT来设置一合适的RTO。显然,在任何时刻连接的RTT都是随机的,无法事先预知。TCP通过测量来获得连接当前RTT的一个估计值,并以该RTT估计值为基准来设置当前的RTO。自适应重传算法的关键就在于对当前RTT的准确估计,以便适时调整RTO。

为了搜集足够的数据来精确地估算当前的RTT,TCP对每个报文都记录下发送出的时间和收到的确认时间。每一个(发送时间,确认时间)对就可以计算出一个RTT测量值的样本(Sample RTT)。TCP为每一个活动的连接都维护一个当前的RTT估计值。该值是对已经过去的一个时间段内该连接的RTT了两只的加权平均,并作为TCP对连接当前实际的RTT值的一种估计。RTT估计值将在发送报文段时被用于确定报文段的RTO。为了保证它能够比较准确地反应当前的网络状态,每当TCP通过测量获得了个新的RTT样本时,都将对RTT的估计值进行更新。不同的更新算法或参数可能获得不同的特性。[1]

最早的TCP曾经用了一个非常简单的公式来估计当前网络的状况,如下

R<-aR+(1-a)MRTP=Rb其中a是一个经验系数为0.1,b通常为2。注意,这是经验,没有推导过程,这个数值是可以被修改的。这个公式是说用旧的RTT(R)和新的RTT (M)综合到一起来考虑新的RTT(R)的大小。但又可以看到,这种估计在网络变化很大的情况下完全不能做出“灵敏的反应”,于是就有下面的修正公式:

Err=M-AA<-A+gErrD<-D+h(|Err|-D)RTO=A+4D,这个递推公式甚至把方差这种统计概念也使用了进来,使得偏差更加的小。而且,必须要指出的是,这两组公式更新,都是在 数据成功传输的情况下才进行,在发生数据重新传输的情况下,并不使用上面的公式进行网络估计,理由很简单,因为程序已经不在正常状态下了,估计出来的数据也是没有意义的。

如果在一个报文段中的数据被一次性地成功传输和确认,那么发送端可以准确得到该报文段传输的RTT样本。但若出现了重传,情况就会变得很复杂。例如,一个报文段发送后出现超时,TCP将在另一个报文段中重传。由于这两个报文段包含了同样的数据,发送方接收到确认信息时将无法分辨出确认信息到底是针对哪个报文段的,因为这两个报文段产生的确认信息可能是完全相同的,确认信息既可能是针对原始报文段的(这种情况可能是由于原报文段或确认在传输中被延迟造成的),也可能是对重传报文段的确认。这种现象称为确认二义性(Acknowledgement Ambiguiity)。确认的二义性将导致TCP无法准确地估算RTT。

为了避免确认二义性带来的问题,TCP采用了Karn算法来维护RTT的估计值。Karn算法规定,TCP只能利用没有确认二义性(既无重发、一次发送成功并得到确认的报文段)的RTT样本来对RTT的估计值进行调整。

 

 

 

2、    坚持计时器

 

    过让接收方指明希望从发送方接收的数据字节数(即窗口大小)来进行流量控制。如果窗口大小为 0,这将有效地阻止发送方传送数据,直到窗口变为非0为止。TCP不对ACK报文段进行确认, TCP只确认那些包含有数据的ACK报文段。如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非 0的窗口),而发送方在等待允许它继续发送数据的窗口更新。为防止这种死锁情况的发生,发送方使用一个坚持定时器 (persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查 (window  probe)。这个报文段只有一个字节的数据。它有一个序号,但它的序号永远不需要确认;甚至在计算对其他部分的数据的确认时该序号也被忽略。探测报文段提醒接收TCP:确认已丢失,必须重传。

坚持计时器的值设置为重传时间的数值。但是,若没有收到从接收端来的响应,则需发送另一个探测报文段,并将坚持计时器的值加倍和复位。发送端继续发送探测报文段,将坚持计时器设定的值加倍和复位,直到这个值增大到门限值(通常是60秒)为止。在这以后,发送端每隔60秒就发送一个探测报文段,直到窗口重新打开。

 

3、    保活计时器

 

        保活计时器使用在某些实现中,用来防止在两个TCP之间的连接出现长时期的空闲。假定客户打开了到服务器的连接,传送了一些数据,然后就保持静默了。也许这个客户出故障了。在这种情况下,这个连接将永远地处理打开状态。

要解决这种问题,在大多数的实现中都是使服务器设置保活计时器。每当服务器收到客户的信息,就将计时器复位。超时通常设置为2小时。若服务器过了2小时还没有收到客户的信息,它就发送探测报文段。若发送了10个探测报文段(每一个相隔75秒)还没有响应,就假定客户出了故障,因而就终止该连接。

这种连接的断开当然不会使用四次握手,而是直接硬性地中断和客户端的TCP连接。

    如果一个给定的连接在两个小时之内没有任何动作,则服务器就向客户发送一个探查报文段。客户机处于以下4种状态之一时,发送探查报文。

1、客户主机依然正常运行,并从服务器可达。客户机的TCP响应正常,而服务器也知道对方正常工作的。服务器在两小时以后将保活定时器复位,如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来2小时复位。

 

2、客户机已经崩溃,并且关闭或正在重新启动,在任何一种情况下,客户的TCP都没有响应,服务器将不能够收到对探查的响应,并在75秒后超时,服务器总共发送10个这样的探查,每个间隔75秒,如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。

socket函数会返回-1,errno设置为ETIMEDOUT,表示连接超时。

 

3、客户主机崩溃并已经重新启动,这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。客户端的TCP发送RST,服务器端收到后关闭此连接。

socket函数会返回-1,errno设置为ECONNRESET,表示连接被对端复位了

 

4、客户机正常运行,但是从服务器不可达,这与状态2相同,因为TCP不能够区分状态4与状态2之间的区别,它所能发现的就是没有收到探查的响应。

服务器不用关注客户主机被关闭和重新启动的情况,当系统被操作员半闭时,所的应用进程也被终止,这会使客户的TCP在连接上发了一个FIN,接收到FIN将使服务器的TCP响服务器进程报告文件结束,使服务器可以检测到这个情况。双方的反应和第二种是一样的,因为服务器不能区分对端异常与中间链路异常。

socket函数会返回-1,errno设置为EHOSTUNREACH,表示对端不可达

 

内核默认并不使用TCPKeepalive功能,除非用户设置了SO_KEEPALIVE选项。

有两种方式可以自行调整保活定时器的参数:一种是修改TCP参数,一种是使用TCP层选项。

 

(1) TCP参数

tcp_keepalive_time

最后一次数据交换到TCP发送第一个保活探测报文的时间,即允许连接空闲的时间,默认为7200s。

tcp_keepalive_intvl

保活探测报文的重传时间,默认为75s。

tcp_keepalive_probes

保活探测报文的发送次数,默认为9次。

 一次完整的保活探测需要花费时间tcp_keepalive_time + tcp_keepalive_intvl*tcp_keepalive_probes,默认值为7875s。

如果觉得两个多小时太长了,可以自行调整上述参数。

 

(2) TCP层选项

TCP_KEEPIDLE:含义同tcp_keepalive_time。

TCP_KEEPINTVL:含义同tcp_keepalive_intvl。

TCP_KEEPCNT:含义同tcp_keepalive_probes。

    TCP参数是面向本机的所有TCP连接,一旦调整了,对所有的连接都有效。

而TCP层选项是面向一条连接的,一旦调整了,只对本条连接有效。

 激活

 在连接建立后,可以通过设置SO_KEEPALIVE选项,来激活保活定时器。

int keepalive = 1;

setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,&keepalive,sizeof(keepalive));

可以使用TCP层选项来动态调整保活定时器的参数。

int keepidle = 600;

int keepintvl = 10;

int keepcnt = 6;

setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &keepidle,sizeof(keepidle));

setsockopt(fd, SOL_TCP, TCP_KEEPINTVL,&keepintvl,sizeof(keepintvl));

setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &keepcnt,sizeof(keepcnt));

 

LinuxSO_KEEPALIVE属性,心跳包

对于面向连接的TCP socket,在实际应用中通常都要检测对端是否处于连接中,连接端口分两种情况:

1、连接正常关闭,调用close() shutdown()连接优雅关闭,send与recv立马返回错误,select返回SOCK_ERR;

2、连接的对端异常关闭,比如网络断掉,突然断电.

对于第二种情况,判断连接是否断开的方法有一下几种:

1、自己编写心跳包程序,简单的说就是自己的程序加入一条线程,定时向对端发送数据包,查看是否有ACK,根据ACK的返回情况来管理连接。此方法比较通用,一般使用业务层心跳处理,灵活可控,但改变了现有的协议;

2、使用TCP的keepalive机制,UNIX网络编程不推荐使用SO_KEEPALIVE来做心跳检测。

keepalive原理:TCP内嵌有心跳包,以服务端为例,当server检测到超过一定时间(/proc/sys/net/ipv4/tcp_keepalive_time7200 即2小时)没有数据传输,那么会向client端发送一个keepalivepacket,此时client端有三种反应:

1、client端连接正常,返回一个ACK.server端收到ACK后重置计时器,在2小时后在发送探测.如果2小时内连接上有数据传输,那么在该时间的基础上向后推延2小时发送探测包;

2、客户端异常关闭,或网络断开。client无响应,server收不到ACK,在一定时间(/proc/sys/net/ipv4/tcp_keepalive_intvl75 即75秒)后重发keepalive packet, 并且重发一定次数(/proc/sys/net/ipv4/tcp_keepalive_probes9 即9次);

3、客户端曾经崩溃,但已经重启.server收到的探测响应是一个复位,server端终止连接。

修改三个参数的系统默认值

临时方法:向三个文件中直接写入参数,系统重启需要重新设置;

临时方法:sysctl -w net.ipv4.tcp_keepalive_intvl=20

全局设置:可更改/etc/sysctl.conf,加上:

net.ipv4.tcp_keepalive_intvl = 20

net.ipv4.tcp_keepalive_probes = 3

net.ipv4.tcp_keepalive_time = 60

有关SO_KEEPALIVE的三个参数详细解释如下:

tcp_keepalive_intvl,保活探测消息的发送频率。默认值为75s。

发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测直到放弃探测确定连接断开的时间,大约为11min。

tcp_keepalive_probes,TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9(次)。

注意:只有设置了SO_KEEPALIVE套接口选项后才会发送保活探测消息。tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测消息的时间,即允许的持续空闲时间。默认值为7200s(2h)。

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/tcp.h>

int keepAlive = 1; // 开启keepalive属性

int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测

int keepInterval = 5; // 探测时发包的时间间隔为5 秒

int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.

setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE,(void*)&keepAlive, sizeof(keepAlive));

setsockopt(rs, SOL_TCP,TCP_KEEPIDLE,(void*)&keepIdle, sizeof(keepIdle));

setsockopt(rs, SOL_TCP, TCP_KEEPINTVL,(void*)&keepInterval, sizeof(keepInterval));

setsockopt(rs, SOL_TCP, TCP_KEEPCNT,(void*)&keepCount, sizeof(keepCount));

 

 

4、    时间等待计时器

 

时间等待计时器是在四次握手的时候使用的。四次握手的简单过程是这样的:假设客户端准备中断连接,首先向服务器端发送一个FIN的请求关闭包(FIN=final),然后由established过渡到FIN-WAIT1状态。服务器收到FIN包以后会发送一个ACK,然后自己由established进入CLOSE-WAIT。此时通信进入半双工状态,即留给服务器一个机会将剩余数据传递给客户端,传递完后服务器发送一个FIN+ACK的包,表示我已经发送完数据可以断开连接了,接着便进入LAST_ACK阶段。客户端收到以后,发送一个ACK表示收到并同意请求,接着由FIN-WAIT2进入TIME-WAIT阶段。服务器端收到ACK,结束连接。此时(即客户端发送完ACK包以后),客户端还要等待2MSL(MSL=maxinumsegmentlifetime 最长报文生存时间,2MSL就是两倍MSL)才能真正关闭连接。

等待2MSL是因为客服端发送的ACK对方可能没有收到,1>此时服务端就要重FIN+ACK包,所以2MSL是从客服端发ACK对方没有到然后服务端重发FIN+ACK的最长时间,等2MSL就是为了保证对方已经收到ACK包了。若不然,客户端提早断开的话服务器端一直重发FIN+ACK,永远无法进入CLOSE状态。

时间等待计时器就是用来记2MSL这个时间的,当计时器到了2MSL以后,客服端才能断开连接。2>能够保证之前某些在网络中滞留很久的发给服务器的报文不会在本次连接连接关闭后再去骚扰服务器。 值得注意的是:最后两次挥手期间,启动了两种计时器,服务器向客户端发送FIN后启动重传计时器,客户端收到FIN后,向服务器发送ACK,同时启动Time-Wait计时器(时间长度为2MSL) 。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值