专栏首页专注网络研发TCP的定时器实现(1)——重传定时器

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

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

TCP的定时器(1)

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

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。

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

 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。

  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}

本文分享自微信公众号 - LinuxerPub(LinuxerPub),作者:glinuxer

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 细说TCP的MSS选项(1)

    前几天,我厂剑英和晓培同学在定位一个TCP通信失败问题时,发现原因是客户端发送的TCP数据过长(1460字节),导致数据包无法成功发送到服务端。...

    glinuxer
  • 容器常用虚拟网络接口之一,veth

    现在容器已经得到了广泛使用,用于实现轻量级的虚拟化,资源的隔离等。Docker则是目前应用最广泛的容器实现。而Docker的实现,依赖于Linu...

    glinuxer
  • 应用层如何强制发送RST即相关内核实现

    前几天群里有个同学问,“如何让应用层强制发送RST中止连接”,而不是通过FIN包的四次交互来关闭连接。当时,我只是凭借以往的经验,猜测使用lin...

    glinuxer
  • MySQL数字类型学习笔记

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    SmileNicky
  • 俄罗斯最大搜索引擎Yandex开源梯度上升机器学习库,背后雄心满满

    安妮 编译自 Tech Crunch 量子位出品 | 公众号 QbitAI 昨天,俄罗斯搜索巨头Yandex开源了Gradient boosting机器学习库C...

    量子位
  • 使用Python解析JSON

    参考链接: Python-Json 3 : python中验证是否为有效JSON数据

    用户7886150
  • LeetCode 1334. 阈值距离内邻居最少的城市(最短路径Dijkstra)

    有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] = [fromi, toi, weighti] 代表 fromi...

    Michael阿明
  • 中兴与英电信公司JT Global签署5G网络协议

    据外媒TelecomLead 11月22日报道,中兴宣布已与英国电信公司JT Global签署了首个5G网络协议。此前中兴已是JT Global的4G网络供应商...

    CloudBest
  • iOS开发中创建一个纵向滑动控件

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

    用户1451823
  • [每日一题]勒索信

    给定一个任意的表示勒索信内容的字符串,和另一个字符串,表示能从杂志中获取到的所有字符,写一个方法判断能否通过剪下杂志中的字符来构造出这封勒索信,若可以,返回 t...

    呼延十

扫码关注云+社区

领取腾讯云代金券