前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TCP的定时器实现(1)——重传定时器

TCP的定时器实现(1)——重传定时器

作者头像
glinuxer
发布2019-04-10 14:58:47
2.2K0
发布2019-04-10 14:58:47
举报
文章被收录于专栏:专注网络研发专注网络研发

微信公众号:LinuxerPub 作者:gfree.wind@gmail.com

TCP的定时器(1)

TCP协议是一个相当复杂的协议,其实现依赖于多个定时器的实现。在TCP套接字的初始化函数tcp_v4_init_sock中,会调用tcp_init_xmit_timers初始化TCP的各个定时器。

代码语言:javascript
复制
1void tcp_init_xmit_timers(struct sock *sk)
2{
3    /* 注册TCP各个定时器的执行函数。 */
4    inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
5                            &tcp_keepalive_timer);
6}

接下来,进入inet_csk_init_xmit_timers。

代码语言:javascript
复制
 1void inet_csk_init_xmit_timers(struct sock *sk,
 2                            void (*retransmit_handler)(unsigned long),
 3                            void (*delack_handler)(unsigned long),
 4                            void (*keepalive_handler)(unsigned long))
 5{
 6    struct inet_connection_sock *icsk = inet_csk(sk);
 7    setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
 8                (unsigned long)sk);
 9    setup_timer(&icsk->icsk_delack_timer, delack_handler,
10                (unsigned long)sk);
11    setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);
12    icsk->icsk_pending = icsk->icsk_ack.pending = 0;
13}

inet_csk_init_xmit_timers代码很简单,就是初始化各个timer,分别是“重传和零窗口探测定时器”、“延迟确认定时器”和“Keep-Alive定时器”。 下面我们将针对每个定时器进行分析。

重传定时器

TCP协议是通过“确认+重传”来保证数据的可靠传输。当对端确认超时后,本端则要进行重传,下面我们来分析重传定时器的执行函数。

代码语言:javascript
复制
 1static void tcp_write_timer(unsigned long data)
 2{
 3    struct sock *sk = (struct sock *)data;
 4    struct inet_connection_sock *icsk = inet_csk(sk);
 5    int event;
 6    bh_lock_sock(sk);
 7    if (sock_owned_by_user(sk)) {
 8        /* 用户进程正在使用这个套接字,那就稍后再试 */
 9        sk_reset_timer(sk, &icsk->icsk_retransmit_timer, jiffies + (HZ / 20));
10        goto out_unlock;
11    }
12    /*
13    套接字状态已经是关闭,或者当前无需执行操作时,直接退出。
14    icsk_pending表示重传定时器要做的事情:目前是重传和0窗口探测。如果为0,则表示没有要做的事情。
15    */
16    if (sk->sk_state == TCP_CLOSE || !icsk->icsk_pending)
17        goto out;
18    /* 未到超时时间,则重新设置重传定时器 */
19    if (time_after(icsk->icsk_timeout, jiffies)) {
20        sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
21        goto out;
22    }
23    /* 获得当前要执行的事件并清零 */
24    event = icsk->icsk_pending;
25    icsk->icsk_pending = 0;
26    switch (event) {
27    case ICSK_TIME_RETRANS:
28        tcp_retransmit_timer(sk);
29        break;
30    case ICSK_TIME_PROBE0:
31        tcp_probe_timer(sk);
32        break;
33    }
34out:
35    sk_mem_reclaim(sk);
36out_unlock:
37    bh_unlock_sock(sk);
38    sock_put(sk);
39}

对于重传来说,进入tcp_retransmit_timer。

代码语言:javascript
复制
  1void tcp_retransmit_timer(struct sock *sk)
  2{
  3    struct tcp_sock *tp = tcp_sk(sk);
  4    struct inet_connection_sock *icsk = inet_csk(sk);
  5    if (!tp->packets_out)
  6        goto out;
  7    WARN_ON(tcp_write_queue_empty(sk));
  8    if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
  9        !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
 10        /* 这是一种意外情况,重传时发现发送窗口为0。*/
 11        struct inet_sock *inet = inet_sk(sk);
 12        if (sk->sk_family == AF_INET) {
 13            LIMIT_NETDEBUG(KERN_DEBUG "TCP: Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
 14                        &inet->inet_daddr, ntohs(inet->inet_dport),
 15            inet->inet_num, tp->snd_una, tp->snd_nxt);
 16        }
 17#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
 18        else if (sk->sk_family == AF_INET6) {
 19            struct ipv6_pinfo *np = inet6_sk(sk);
 20            LIMIT_NETDEBUG(KERN_DEBUG "TCP: Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
 21                        &np->daddr, ntohs(inet->inet_dport),
 22                        inet->inet_num, tp->snd_una, tp->snd_nxt);
 23        }
 24#endif
 25        /* 如果已经有TCP_RTO_MAX时间,没有收到对端消息,则设置TCP套接字为错误状态。 */
 26        if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
 27            tcp_write_err(sk);
 28            goto out;
 29        }
 30        /* TCP进入loss状态 */
 31        tcp_enter_loss(sk, 0);
 32        /* 重传发送队列中的第一个报文,即未确认收到的第一个报文 */
 33        tcp_retransmit_skb(sk, tcp_write_queue_head(sk));
 34        __sk_dst_reset(sk);
 35        goto out_reset_timer;
 36    }
 37    /* 发送超时的处理 */
 38    if (tcp_write_timeout(sk))
 39        goto out;
 40    if (icsk->icsk_retransmits == 0) {
 41        int mib_idx;
 42        /* 流控状态的SNMP信息更新 */
 43        if (icsk->icsk_ca_state == TCP_CA_Recovery) {
 44            if (tcp_is_sack(tp))
 45                mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
 46            else
 47                mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
 48        } else if (icsk->icsk_ca_state == TCP_CA_Loss) {
 49            mib_idx = LINUX_MIB_TCPLOSSFAILURES;
 50        } else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
 51                tp->sacked_out) {
 52            if (tcp_is_sack(tp))
 53                mib_idx = LINUX_MIB_TCPSACKFAILURES;
 54            else
 55                mib_idx = LINUX_MIB_TCPRENOFAILURES;
 56        } else {
 57                mib_idx = LINUX_MIB_TCPTIMEOUTS;
 58        }
 59        NET_INC_STATS_BH(sock_net(sk), mib_idx);
 60    }
 61    /* 根据是否使用frto,即虚假超时判断算法,进入不同的状态 */
 62    if (tcp_use_frto(sk)) {
 63        tcp_enter_frto(sk);
 64    } else {
 65        tcp_enter_loss(sk, 0);
 66    }
 67    /* 重传发送队列的第一个报文 */
 68    if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk)) > 0) {
 69        /* 因为本地拥塞而重传失败,则无需backoff定时器 */
 70        if (!icsk->icsk_retransmits)
 71            icsk->icsk_retransmits = 1;
 72        inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
 73                                min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
 74                                TCP_RTO_MAX);
 75        goto out;
 76    }
 77    icsk->icsk_backoff++;
 78    icsk->icsk_retransmits++;
 79    out_reset_timer:
 80    /* 满足下面的所有条件时,则使用线性超时时间,即每次超时的时间是一样的。
 81    1. 连接是已连接状态
 82    2. 套接字配置了线性超时选项或者系统配置了线性超时
 83    3. 当前tcp是一个“瘦的”,即低速的TCP连接
 84    4. 超时个数小于指定值
 85    */
 86    if (sk->sk_state == TCP_ESTABLISHED &&
 87        (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
 88        tcp_stream_is_thin(tp) &&
 89        icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
 90        icsk->icsk_backoff = 0;
 91        icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
 92    } else {
 93        /* 正常指数后退超时 */
 94        icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
 95    }
 96    /* 重设超时定时器 */
 97    inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
 98    /* 连重传都超时了,则重置套接字的下一跳。这样后面可以重新选择路由,避免因为路由问题导致报文无法到达对端 */
 99    if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1, 0, 0))
100        __sk_dst_reset(sk);
101out:;
102}

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LinuxerPub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TCP的定时器(1)
    • 重传定时器
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档