TCP定时器 之 重传定时器
注:这部分还没有完全分析透彻,先在此记录,后面回顾的时候再进行补充;
启动定时器:
(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定时器 之 重传定时器的更多相关文章
- 【网络协议】TCP中的四大定时器
前言 对于每个TCP连接,TCP一般要管理4个不同的定时器:重传定时器.坚持定时器.保活定时器.2MSL定时器. 重传定时器 非常明显重传定时器是用来计算TCP报文段的超时重传时间的(至于超时重传时间 ...
- TCP的四种定时器简单记录
TCP管理的4个不同的定时器: 1.重传定时器:用于当希望收到另一端的确认. 2.坚持定时器:使窗口大小信息保持不断流动. 3.保活定时器:检测TCP空闲连接的另一端何时崩溃或重启. 4.2MSL定时 ...
- tcp中的常见定时器
(1)超时重传定时器tcp的靠谱特性,通过确认机制,保证每一个包都被对方收到,那么什么时候需要重传呢?就是靠这个超时重传定时器,每次发送报文前都启动这个定时器,如果定时器超时之前收到了应答则关闭定时器 ...
- TCP的定时器系列 — 超时重传定时器
主要内容:TCP定时器概述,超时重传定时器.ER延迟定时器.PTO定时器的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd Q:一条TCP连接会使用 ...
- TCP系列13—重传—3、协议中RTO计算和RTO定时器维护
从上一篇示例中我们可以看到在TCP中有一个重要的过程就是决定何时进行超时重传,也就是RTO的计算更新.由于网络状况可能会受到路由变化.网络负载等因素的影响,因此RTO也必须跟随网络状况动态更新.如果T ...
- TCP定时器 之 重传/延迟ACK/保活 定时器初始化
创建socket时会创建传输控制块,之后调用初始化函数对控制块进行初始化,其中包括对定时器的初始化,tcp会调用tcp_init_xmit_timers函数来初始化这些定时器,本文将详细分析tcp_i ...
- 动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...
- 14.TCP的坚持定时器和保活定时器
一.坚持定时器 1.坚持定时器的由来 TCP通过让接收方指明希望从发送方接受的窗口大小来进行流量控制.设置窗口大小为0可以组织发送方传送数据,直至窗口变为非0为止. ...
- TCP的定时器系列 — 保活定时器
主要内容:保活定时器的实现,TCP_USER_TIMEOUT选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 原理 HTTP有Keepaliv ...
随机推荐
- nginx(五)- linux下安装nginx与配置
linux系统为Centos 64位 准备目录 [root@instance-3lm099to ~]# mkdir /usr/local/nginx [root@instance-3lm099to ~ ...
- SQL函数取汉字拼音首字母
)='') ) as begin ), ) , ,) if @chn > 'z' if( @chn < '八' ) set @c = 'A' else if ( @chn < '嚓' ...
- java8学习之自定义收集器深度剖析与并行流陷阱
自定义收集器深度剖析: 在上次[http://www.cnblogs.com/webor2006/p/8342427.html]中咱们自定义了一个收集器,这对如何使用收集器Collector是极有帮助 ...
- 如何在当前目录下打开Windows cmd?
在当前目录下,按Alt + D (全选当前目录),然后输入 cmd 再按回车 Enter .
- 代码审计-DVWA-命令注入
首先说明,我水平不高,这是我在学习代码审计过程中写的记录笔记,难免有不正之处,还望指出. Windows 10 php7.2.10 + apache DVWA代码审计 命令执行 low <?ph ...
- python时间 time模块和datetime模块
一.time模块 time模块中时间表现的格式主要有三种: a.timestamp时间戳,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量 b.struct_time时间元组,共 ...
- 前端之HTML初识
目录 手写服务端,启用浏览器(客户端连接服务端) Web服务的本质: HTTP协议(HyperText Transfer Protocol) HMTL(HyperText Mark Language) ...
- C# out关键字
在c#中"out"关键字可以通过参数一次返回多个值. using System; namespace ConsoleApplication1 { internal class Pr ...
- wx小程序知识点(六)
六.生命周期 (1)onLoad —— 加载时触发,只调用一次,可用来发送请求绑定数据.获取url中参数 (2)onShow —— 页面显示时触发,每次显示都会执行,用来获取需要频繁更新的数 ...
- CSS定位——文档流定位
关于CSS的定位机制Ⅰ ㈠概念 对于盒子模型来说,也就是页面元素,这些盒子究竟在页面的什么位置,怎样排列它,那么找到它的位置,确定它的位置,这个就是定位机制所决定的. ㈡分类 文档流, 浮动定位,层定 ...