注:这部分还没有完全分析透彻,先在此记录,后面回顾的时候再进行补充;

启动定时器:

(1) 之前发送的数据段已经得到确认,新发出一个数据段之后设定;

(2) 新建连接发送syn之后设定;

(3) PMTU探测失败之后设定;

(4) 接收方丢弃SACK部分接收的段时设定;

定时器回调函数:

重传定时器超时回调,根据连接控制块中不同的事件类型来分别调用不同的函数进行处理,这里我们只关心ICSK_TIME_RETRANS类型(重传类型),重传细节会继续调用函数tcp_retransmit_timer进行下一步的处理;

 /* Called with bottom-half processing disabled.
Called by tcp_write_timer() */
void tcp_write_timer_handler(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
int event; /* 连接处于CLOSE或者LISTEN状态或者 没有指定待处理事件类型 */
if ((( << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
!icsk->icsk_pending)
goto out; /* 超时时间未到,则重新设置定时器超时时间 */
if (time_after(icsk->icsk_timeout, jiffies)) {
sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
goto out;
} /* 获取事件类型 */
event = icsk->icsk_pending; switch (event) {
case ICSK_TIME_REO_TIMEOUT:
tcp_rack_reo_timeout(sk);
break;
case ICSK_TIME_LOSS_PROBE:
tcp_send_loss_probe(sk);
break;
/* 重传事件 */
case ICSK_TIME_RETRANS:
icsk->icsk_pending = ;
tcp_retransmit_timer(sk);
break;
case ICSK_TIME_PROBE0:
icsk->icsk_pending = ;
tcp_probe_timer(sk);
break;
} out:
sk_mem_reclaim(sk);
}

tcp_retransmit_timer函数即为超时重传的核心函数,其根据不同的情况决定是否进行重传,并且调整重传次数和退避指数,设定下一次重传定时器等;

 /**
* tcp_retransmit_timer() - The TCP retransmit timeout handler
* @sk: Pointer to the current socket.
*
* This function gets called when the kernel timer for a TCP packet
* of this socket expires.
*
* It handles retransmission, timer adjustment and other necesarry measures.
*
* Returns: Nothing (void)
*/
void tcp_retransmit_timer(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
struct inet_connection_sock *icsk = inet_csk(sk); /* fastopen请求控制块不为空 */
if (tp->fastopen_rsk) {
WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
sk->sk_state != TCP_FIN_WAIT1);
/* fastopen重传syn+ack */
tcp_fastopen_synack_timer(sk);
/* Before we receive ACK to our SYN-ACK don't retransmit
* anything else (e.g., data or FIN segments).
*/
return;
} /* 发送队列列出的段都已经得到确认 */
if (!tp->packets_out)
goto out; WARN_ON(tcp_write_queue_empty(sk)); tp->tlp_high_seq = ; /*
对端窗口为0,套接口状态不是DEAD,
连接不是出于连接过程中的状态
*/
if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
!(( << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
/* Receiver dastardly shrinks window. Our retransmits
* become zero probes, but we should not timeout this
* connection. If the socket is an orphan, time it out,
* we cannot allow such beasts to hang infinitely.
*/
struct inet_sock *inet = inet_sk(sk);
if (sk->sk_family == AF_INET) {
net_dbg_ratelimited("Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
&inet->inet_daddr,
ntohs(inet->inet_dport),
inet->inet_num,
tp->snd_una, tp->snd_nxt);
}
#if IS_ENABLED(CONFIG_IPV6)
else if (sk->sk_family == AF_INET6) {
net_dbg_ratelimited("Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
&sk->sk_v6_daddr,
ntohs(inet->inet_dport),
inet->inet_num,
tp->snd_una, tp->snd_nxt);
}
#endif
/* 接收时间已经超过了TCP_RTO_MAX,出错 */
if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
tcp_write_err(sk);
goto out;
} /* 进入loss状态 */
tcp_enter_loss(sk); /* 发送重传队列的第一个数据段 */
tcp_retransmit_skb(sk, tcp_write_queue_head(sk), ); /* 重置路由缓存 */
__sk_dst_reset(sk);
goto out_reset_timer;
} /* 重传检查 */
if (tcp_write_timeout(sk))
goto out; /* 重传次数为0,第一次进入重传 */
if (icsk->icsk_retransmits == ) {
int mib_idx; /* 不同拥塞状态的数据统计 */ if (icsk->icsk_ca_state == TCP_CA_Recovery) {
if (tcp_is_sack(tp))
mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
else
mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
} else if (icsk->icsk_ca_state == TCP_CA_Loss) {
mib_idx = LINUX_MIB_TCPLOSSFAILURES;
} else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
tp->sacked_out) {
if (tcp_is_sack(tp))
mib_idx = LINUX_MIB_TCPSACKFAILURES;
else
mib_idx = LINUX_MIB_TCPRENOFAILURES;
} else {
mib_idx = LINUX_MIB_TCPTIMEOUTS;
}
__NET_INC_STATS(sock_net(sk), mib_idx);
} /* 进入loss阶段 */
tcp_enter_loss(sk); /* 发送重传队列的第一个数据段失败 */
if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk), ) > ) {
/* Retransmission failed because of local congestion,
* do not backoff.
*/
/* 更新重传数 */
if (!icsk->icsk_retransmits)
icsk->icsk_retransmits = ; /* 复位定时器,等待下次重传 */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
TCP_RTO_MAX);
goto out;
} /* Increase the timeout each time we retransmit. Note that
* we do not increase the rtt estimate. rto is initialized
* from rtt, but increases here. Jacobson (SIGCOMM 88) suggests
* that doubling rto each time is the least we can get away with.
* In KA9Q, Karn uses this for the first few times, and then
* goes to quadratic. netBSD doubles, but only goes up to *64,
* and clamps at 1 to 64 sec afterwards. Note that 120 sec is
* defined in the protocol as the maximum possible RTT. I guess
* we'll have to use something other than TCP to talk to the
* University of Mars.
*
* PAWS allows us longer timeouts and large windows, so once
* implemented ftp to mars will work nicely. We will have to fix
* the 120 second clamps though!
*/
/* 递增退避指数和重传次数 */
icsk->icsk_backoff++;
icsk->icsk_retransmits++; out_reset_timer:
/* If stream is thin, use linear timeouts. Since 'icsk_backoff' is
* used to reset timer, set to 0. Recalculate 'icsk_rto' as this
* might be increased if the stream oscillates between thin and thick,
* thus the old value might already be too high compared to the value
* set by 'tcp_set_rto' in tcp_input.c which resets the rto without
* backoff. Limit to TCP_THIN_LINEAR_RETRIES before initiating
* exponential backoff behaviour to avoid continue hammering
* linear-timeout retransmissions into a black hole
*/ if (sk->sk_state == TCP_ESTABLISHED &&
(tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
tcp_stream_is_thin(tp) &&
icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
/* 退避指数清0 */
icsk->icsk_backoff = ;
/* 重传超时时间不变 */
icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
} else {
/* Use normal (exponential) backoff */
/* 重传超时时间*2 */
icsk->icsk_rto = min(icsk->icsk_rto << , TCP_RTO_MAX);
} /* 复位定时器,等待下次重传 */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX); /* 重传超时重置路由缓存 */
if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1 + , , ))
__sk_dst_reset(sk); out:;
}

tcp_write_timeout为重传超时情况的判断,函数根据不同情况,获取最大重传次数,并且通过该次数获取最大的超时时间,若发送时间超过了该最大超时时间,则断开连接;

 /* A write timeout has occurred. Process the after effects. */
static int tcp_write_timeout(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
int retry_until;
bool do_reset, syn_set = false; /* 连接建立过程中 */
if (( << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
/* 已经重传过 */
if (icsk->icsk_retransmits) { /* 更新路由缓存项 */
dst_negative_advice(sk); /* fastopen缓存 */
if (tp->syn_fastopen || tp->syn_data)
tcp_fastopen_cache_set(sk, , NULL, true, );
if (tp->syn_data && icsk->icsk_retransmits == )
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENACTIVEFAIL);
} else if (!tp->syn_data && !tp->syn_fastopen) {
sk_rethink_txhash(sk);
} /* 重传最大次数 */
retry_until = icsk->icsk_syn_retries ? : net->ipv4.sysctl_tcp_syn_retries;
syn_set = true;
} else {
/* 重传次数超过retries1,黑洞? */
if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1, , )) {
/* Some middle-boxes may black-hole Fast Open _after_
* the handshake. Therefore we conservatively disable
* Fast Open on this path on recurring timeouts after
* successful Fast Open.
*/
if (tp->syn_data_acked) {
tcp_fastopen_cache_set(sk, , NULL, true, );
if (icsk->icsk_retransmits == net->ipv4.sysctl_tcp_retries1)
NET_INC_STATS(sock_net(sk),
LINUX_MIB_TCPFASTOPENACTIVEFAIL);
}
/* Black hole detection */ /* PMTU探测 */
tcp_mtu_probing(icsk, sk); /* 更新路由缓存 */
dst_negative_advice(sk);
} else {
sk_rethink_txhash(sk);
} /* 连接已建立重传次数 */
retry_until = net->ipv4.sysctl_tcp_retries2; /* 套接口在关闭状态 */
if (sock_flag(sk, SOCK_DEAD)) { /* rto < 最大值 */
const bool alive = icsk->icsk_rto < TCP_RTO_MAX; /* 获取重传次数 */
retry_until = tcp_orphan_retries(sk, alive); /* 连接超时判断 */
do_reset = alive ||
!retransmits_timed_out(sk, retry_until, , ); /* 孤儿socket超过资源限制 */
if (tcp_out_of_resources(sk, do_reset))
return ;
}
} /* 判断连接是否超时 */
if (retransmits_timed_out(sk, retry_until,
syn_set ? : icsk->icsk_user_timeout, syn_set)) {
/* Has it gone just too far? */
tcp_write_err(sk);
return ;
}
return ;
}
 /**
* retransmits_timed_out() - returns true if this connection has timed out
* @sk: The current socket
* @boundary: max number of retransmissions
* @timeout: A custom timeout value.
* If set to 0 the default timeout is calculated and used.
* Using TCP_RTO_MIN and the number of unsuccessful retransmits.
* @syn_set: true if the SYN Bit was set.
*
* The default "timeout" value this function can calculate and use
* is equivalent to the timeout of a TCP Connection
* after "boundary" unsuccessful, exponentially backed-off
* retransmissions with an initial RTO of TCP_RTO_MIN or TCP_TIMEOUT_INIT if
* syn_set flag is set.
*
*/
static bool retransmits_timed_out(struct sock *sk,
unsigned int boundary,
unsigned int timeout,
bool syn_set)
{
unsigned int linear_backoff_thresh, start_ts; /* 设置基础超时时间 */
unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN; /* 未发生过重传 */
if (!inet_csk(sk)->icsk_retransmits)
return false; /* 开始时间设置为数据包发送时间戳 */
start_ts = tcp_sk(sk)->retrans_stamp; /* 开始时间为0,则设置为第一个sk的 */
if (unlikely(!start_ts))
start_ts = tcp_skb_timestamp(tcp_write_queue_head(sk)); /* syn包timeout为0,非syn包tcp_user_timeout为0 */
if (likely(timeout == )) { /* 指数退避次数 */
linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base); /* 根据重传次数boudany计算超时时间 */
if (boundary <= linear_backoff_thresh)
timeout = (( << boundary) - ) * rto_base;
else
timeout = (( << linear_backoff_thresh) - ) * rto_base +
(boundary - linear_backoff_thresh) * TCP_RTO_MAX;
} /* 经过的时间是否超过了超时时间 */
return (tcp_time_stamp - start_ts) >= timeout;
}

TCP定时器 之 重传定时器的更多相关文章

  1. 【网络协议】TCP中的四大定时器

    前言 对于每个TCP连接,TCP一般要管理4个不同的定时器:重传定时器.坚持定时器.保活定时器.2MSL定时器. 重传定时器 非常明显重传定时器是用来计算TCP报文段的超时重传时间的(至于超时重传时间 ...

  2. TCP的四种定时器简单记录

    TCP管理的4个不同的定时器: 1.重传定时器:用于当希望收到另一端的确认. 2.坚持定时器:使窗口大小信息保持不断流动. 3.保活定时器:检测TCP空闲连接的另一端何时崩溃或重启. 4.2MSL定时 ...

  3. tcp中的常见定时器

    (1)超时重传定时器tcp的靠谱特性,通过确认机制,保证每一个包都被对方收到,那么什么时候需要重传呢?就是靠这个超时重传定时器,每次发送报文前都启动这个定时器,如果定时器超时之前收到了应答则关闭定时器 ...

  4. TCP的定时器系列 — 超时重传定时器

    主要内容:TCP定时器概述,超时重传定时器.ER延迟定时器.PTO定时器的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd Q:一条TCP连接会使用 ...

  5. TCP系列13—重传—3、协议中RTO计算和RTO定时器维护

    从上一篇示例中我们可以看到在TCP中有一个重要的过程就是决定何时进行超时重传,也就是RTO的计算更新.由于网络状况可能会受到路由变化.网络负载等因素的影响,因此RTO也必须跟随网络状况动态更新.如果T ...

  6. TCP定时器 之 重传/延迟ACK/保活 定时器初始化

    创建socket时会创建传输控制块,之后调用初始化函数对控制块进行初始化,其中包括对定时器的初始化,tcp会调用tcp_init_xmit_timers函数来初始化这些定时器,本文将详细分析tcp_i ...

  7. 动手学习TCP:4种定时器

    上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...

  8. 14.TCP的坚持定时器和保活定时器

    一.坚持定时器   1.坚持定时器的由来         TCP通过让接收方指明希望从发送方接受的窗口大小来进行流量控制.设置窗口大小为0可以组织发送方传送数据,直至窗口变为非0为止.         ...

  9. TCP的定时器系列 — 保活定时器

    主要内容:保活定时器的实现,TCP_USER_TIMEOUT选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 原理 HTTP有Keepaliv ...

随机推荐

  1. Jquery table相关--工时系统

    1.jquery 的弹出对话框,单击事件之后 if (confirm("确定要删除?")) { // //点击确定后操作 } 2.对某个table中的checkbox是否被选中的遍 ...

  2. linux之信息查看

    在使用Linux操作系统的时候,有时候会需要了解当前使用的系统版本信息,特别是在给别人进行服务器部署运维的时候,准确的系统版本信息至关重要 查看linux内核版本信息: cat  /proc/vers ...

  3. JS代码格式化

    JS代码格式化也就是规范化,保留必要的换行和缩进使代码阅读起来更容易.团队协同工作时会有相应的标准,大家要保证统一的代码风格,这样在合并代码的时候才不容易出问题.通过快捷键Ctrl+Shift+F进行 ...

  4. Caffe中im2col的实现解析

    这里,我是将Caffe中im2col的解析过程直接拉了出来,使用C++进行了输出,方便理解.代码如下: #include<iostream> using namespace std; bo ...

  5. 免费使用Google

    这里需要借助一下`梯子`,这里有教程 点击进入 如果没有谷歌浏览器,进入下载最新版谷歌浏览器,进入下载,不要移动它的安装位置,选择默认位置, 如果已经安装了谷歌浏览器,打开赛风之后,选择设置 进行安装 ...

  6. Django的MySQL Driver配置

    PEP 249规定了Python的数据库API.MySQL主要有三种API实现: MySQLdb 是Andy Dustman开发的.使用原生代码/C语言绑定的驱动,它已经开发了数十年. mysqlcl ...

  7. dedecms 后台栏目全部展开 包括三级栏目

    include/typeunit.class.admin.php 搜索以下代码并删除 style='display:none'

  8. Linux基本命令之Vim

    在vim,vi,gedit编辑器中显示行号:        在命令模式下:set nu 取消行号:set nonu 参照博客:https://www.cnblogs.com/Mr0wang/p/728 ...

  9. UVA - 11996 Jewel Magic (Treap+二分哈希)

    维护一个01序列,一共四种操作: 1.插入一个数 2.删除一个数 3.反转一个区间 4.查询两个后缀的LCP 用Splay或者Treap都可以做,维护哈希值,二分求LCP即可. 注意反转序列的时候序列 ...

  10. HihoCoder1052基因工程(简单模拟题)

    描述 小Hi和小Ho正在进行一项基因工程实验.他们要修改一段长度为N的DNA序列,使得这段DNA上最前面的K个碱基组成的序列与最后面的K个碱基组成的序列完全一致. 例如对于序列"ATCGAT ...