TCP协议相关的定时器

参考博客:http://blog.qiusuo.im/blog/2014/03/19/tcp-timeout/

                 http://perthcharles.github.io/2015/09/07/wiki-tcp-retries/

在《TCP/IP详解 卷1:协议》第21章节 TCP的超时和重传中介绍了对于每一个连接,TCP管理4个不同的定时器:

(1)重传定时器:当收不到对端ACK时,定时重发

(2)坚持定时器:发送方使用坚持定时器周期性地向接收方查询窗口大小,防止通告窗口非0的报文丢失

(3)保活定时器:定时检测连接的另一端是否崩溃或重启

(4)2MSL定时器测量一个连接是否处于TIME_WAIT的时间,2MSL时长无法修改,除非重新编译内核。

在《TCP/IP详解 卷1:协议》第18章节介绍了TCP状态迁移,后面介绍各个状态的超时情况在Linux中的实现


1. Connection-Establishment Timer

在TCP三次握手创建一个连接时,以下两种情况会发生超时:

  1. client发送SYN后,进入SYN_SENT状态,等待server的SYN+ACK。
  2. server收到连接创建的SYN,回应SYN+ACK后,进入SYN_RECD状态,等待client的ACK。

当超时发生时,就会重传,一直到75s还没有收到任何回应,便会放弃,终止连接的创建。但是在Linux实现中,并不是依靠超时总时间来判断是否终止连接。而是依赖重传次数:

  • tcp_syn_retries (integer; default: 5; since Linux 2.2)

    The maximum number of times initial SYNs for an active TCP connection attempt will be retransmitted. This value should not be higher than 255. The default value is 5, which corresponds to approximately 180 seconds.

  • tcp_synack_retries (integer; default: 5; since Linux 2.2)

    The maximum number of times a SYN/ACK segment for a passive TCP connection will be retransmitted. This number should not be higher than 255.

2. Retransmission Timer

当三次握手成功,连接建立,发送TCP segment,等待ACK确认。如果在指定时间内,没有得到ACK,就会重传,一直重传到放弃为止。Linux中也有相关变量来设置这里的重传次数的:

  • tcp_retries1 (integer; default: 3; since Linux 2.2)

    The number of times TCP will attempt to retransmit a packet on an established connection normally, without the extra effort of getting the network layers involved. Once we exceed this number of retransmits, we first have the network layer update the route if possible before each new retransmit. The default is the RFC specified minimum of 3.

  • tcp_retries2 (integer; default: 15; since Linux 2.2)

    The maximum number of times a TCP packet is retransmitted in established state before giving up. The default value is 15, which corresponds to a duration of approxi‐mately between 13 to 30 minutes, depending on the retransmission timeout. The RFC 1122 specified minimum limit of 100 seconds is typically deemed too short.

tcp_retries1表示在多少次重传后,进行路由更新缓存,以避免由于路由选路变化带来的问题
// RTO timer的处理函数是tcp_retransmit_timer(),与tcp_retries1相关的代码调用关系如下  
tcp_retransmit_timer()
    => tcp_write_timeout()  // 判断是否重传了足够的久
        => retransmit_timed_out(sk, sysctl_tcp_retries1, 0, 0)  // 判断是否超过了阈值

// tcp_write_timeout()的具体相关内容  
...
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    // 如果超时发生在三次握手期间,此时有专门的tcp_syn_retries来负责限定重传次数
    ...
} else {    // 如果超时发生在数据发送期间
    // 这个函数负责判断重传是否超过阈值,返回真表示超过。后续会详细分析这个函数  
    if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) { 
        /* Black hole detection */
        tcp_mtu_probing(icsk, sk);  // 如果开启tcp_mtu_probing(默认关闭)了,则执行PMTU

        dst_negative_advice(sk);    // 更新路由缓存
    }
    ...

这里面只设置的重传次数,重传间隔根据实际的RTT计算:RTO = A + 4D,A表示平滑的RTT,D表示平滑的RTT均值的偏差。

重传总时长是由重传次数和RTO计算出来的,具体算法如下:

    // 如果用户态未指定timeout,则算一个出来
    if (likely(timeout == 0)) {
            /* 下面的计算过程,其实就是算一下如果以rto_base为第一次重传间隔,
             * 重传boundary次需要多长时间
             */
            linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);

            if (boundary <= linear_backoff_thresh)
                    timeout = ((2 << boundary) - 1) * rto_base;
            else
                    timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
                            (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
    }
    // 如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,则认为重传超于阈值了
    return (tcp_time_stamp - start_ts) >= timeout;

3. Delayed ACK Timer 

当一方接受到TCP segment,需要回应ACK。但是不需要 立即 发送,而是等上一段时间,看看是否有其他数据可以 捎带 一起发送。这段时间便是 Delayed ACK Timer ,一般为200ms。这是为了减轻网络负载,可以等待有无数据发送和收到新数据。

4. Persist Timer

如果某一时刻,一方发现自己的 socket read buffer 满了,无法接受更多的TCP data,此时就是在接下来的发送包中指定通告窗口的大小为0,这样对方就不能接着发送TCP data了。如果socket read buffer有了空间,可以重设通告窗口的大小在接下来的 TCP segment 中告知对方。可是万一这个 TCP segment 不附带任何data(即ACK报文),所以即使这个segment丢失也不会知晓(ACKs are not acknowledged, only data is acknowledged)。对方没有接受到,便不知通告窗口的大小发生了变化,也不会发送TCP data。这样双方便会一直僵持下去。

TCP协议采用这个机制避免这种问题:对方即使知道当前不能发送TCP data,当有data发送时,过一段时间后,也应该尝试发送一个字节。这段时间便是 Persist Timer 。

5. Keepalive Timer

TCP socket 的 SO_KEEPALIVE option,主要适用于这种场景:连接的双方一般情况下没有数据要发送,仅仅就想尝试确认对方是否依然在线。目前vipbar网吧,判断当前客户端是否依然在线,就用的是这个option。

具体实现方法:当tcp_keepalive_time超时后,TCP每隔一段时间(tcp_keepalive_intvl)会发送一个特殊的 Probe Segment,强制对方回应,如果没有在指定的时间内回应,便会重传,一直到重传次数达到 tcp_keepalive_probes 便认为对方已经crash了。

  • tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)

    The number of seconds between TCP keep-alive probes.

  • tcp_keepalive_probes (integer; default: 9; since Linux 2.2)

    The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end.

  • tcp_keepalive_time (integer; default: 7200; since Linux 2.2)

    The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are only sent when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval of 75 sec‐onds apart) when keep-alive is enabled.

Note that underlying connection tracking mechanisms and application timeouts may be much shorter.

6. FIN_WAIT_2 Timer

当主动关闭方想关闭TCP connection,发送FIN并且得到相应ACK,从FIN_WAIT_1状态进入FIN_WAIT_2状态,此时不能发送任何data了,只等待对方发送FIN。可以万一对方一直不发送FIN呢?这样连接就一直处于FIN_WAIT_2状态,也是很经典的一个DoS。因此需要一个Timer,超过这个时间,就放弃这个TCP connection了。

  • tcp_fin_timeout (integer; default: 60; since Linux 2.2)

    This specifies how many seconds to wait for a final FIN packet before the socket is forcibly closed. This is strictly a violation of the TCP specification, but required to prevent denial-of-service attacks. In Linux 2.2, the default value was 180.

7. TIME_WAIT Timer

TIME_WAIT Timer存在的原因和必要性,主要是两个方面:

  1. 主动关闭方发送了一个ACK给对方,假如这个ACK发送失败,并导致对方重发FIN信息,那么这时候就需要TIME_WAIT状态来维护这次连接,因为假如没有TIME_WAIT,当重传的FIN到达时,TCP连接的信息已经不存在,所以就会重新启动消息应答,会导致对方进入错误的状态而不是正常的终止状态。假如主动关闭方这时候处于TIME_WAIT,那么仍有记录这次连接的信息,就可以正确响应对方重发的FIN了。
  2. 一个数据报在发送途中或者响应过程中有可能成为残余的数据报,因此必须等待足够长的时间避免新的连接会收到先前连接的残余数据报,而造成状态错误。

TIME_WAIT超时时长固定为2MSL,无法修改,除非修改内核重新编译。有的应用场景需要快速回收socket,减少socket处于TIME_WAIT的状态,可以通过修改tcp_tw_reuse参数来重用TIME_WAIT状态的socket



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值