坚持定时器在接收方通告接收窗口为0,阻止发送端继续发送数据时设定。

由于连接接收端的发送窗口通告不可靠(只有数据才会确认,ACK不会确认),如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接收放等待接收数据(因为它已经向发送方通过了一个非0窗口),而发送方在等待允许它继续发送数据的窗口更新。

为了防止上面的情况,发送方在接收到0窗口通告后,启动一个坚持定时器来周期的发送1字节的数据,以便发现接收方窗口是否已经增大。这些从发送方发出的报文段称为窗口探测;

下面来分析坚持定时器的实现代码:

启动定时器:

通过inet_csk_reset_xmit_timer来启动坚持定时器,其类型设置为ICSK_TIME_PROBE0,其最终通过icsk_retransmit_timer重传定时器来实现,只是通过类型来区分当前使用的是哪种定时器,以及超时时需要分给哪个对应类型的回调;

 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, when, TCP_RTO_MAX);
 /*
* Reset the retransmission timer
*/
static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,
unsigned long when,
const unsigned long max_when)
{
struct inet_connection_sock *icsk = inet_csk(sk); if (when > max_when) {
when = max_when;
} if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 ||
what == ICSK_TIME_EARLY_RETRANS || what == ICSK_TIME_LOSS_PROBE ||
what == ICSK_TIME_REO_TIMEOUT) {
icsk->icsk_pending = what;
icsk->icsk_timeout = jiffies + when;
sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
} else if (what == ICSK_TIME_DACK) {
icsk->icsk_ack.pending |= ICSK_ACK_TIMER;
icsk->icsk_ack.timeout = jiffies + when;
sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
} }

定时器回调函数:

因为是共用重传定时器,所以超时会进入到超时定时器的处理流程,在进入tcp_write_timer_handler后才会根据定时器的类型来区分出该定时器是坚持定时器,进而调用tcp_probe_timer来进行对应的处理;

 static void tcp_write_timer(unsigned long data)
{
struct sock *sk = (struct sock *)data; bh_lock_sock(sk);
/* 没被用户系统调用锁定 */
if (!sock_owned_by_user(sk)) {
/* 调用超时处理函数 */
tcp_write_timer_handler(sk);
} else {
/* delegate our work to tcp_release_cb() */
/* 交给tcp_release_cb处理 */
if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &sk->sk_tsq_flags))
sock_hold(sk);
}
bh_unlock_sock(sk);
sock_put(sk);
}
 /* 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;
/* 坚持定时器探测0窗口 */
case ICSK_TIME_PROBE0:
icsk->icsk_pending = ;
tcp_probe_timer(sk);
break;
} out:
sk_mem_reclaim(sk);
}

tcp_probe_timer函数主要对是否发送探测进行各种情况的检查,包括无需额外探测,时间戳检查,重试次数检查,孤儿套接字资源检查等,检查失败则关闭连接,成功则发送探测数据段;

 static void tcp_probe_timer(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
int max_probes;
u32 start_ts; /*
(1)如果存在发送出去未被确认的段,
要么被确认返回窗口,要么重传,无需额外构造探测包
(2)或者发送队列没有待发送的段,无数据需要发,
不关心窗口情况
则无需另外组织探测数据
*/
if (tp->packets_out || !tcp_send_head(sk)) {
/* 探测次数清零 */
icsk->icsk_probes_out = ;
return;
} /* RFC 1122 4.2.2.17 requires the sender to stay open indefinitely as
* long as the receiver continues to respond probes. We support this by
* default and reset icsk_probes_out with incoming ACKs. But if the
* socket is orphaned or the user specifies TCP_USER_TIMEOUT, we
* kill the socket when the retry count and the time exceeds the
* corresponding system limit. We also implement similar policy when
* we use RTO to probe window in tcp_retransmit_timer().
*/
/* 待发送数据的时间戳 */
start_ts = tcp_skb_timestamp(tcp_send_head(sk));
/* 时间戳为0,设置一下 */
if (!start_ts)
skb_mstamp_get(&tcp_send_head(sk)->skb_mstamp);
/* 有时间戳则判断是否超过了用户设置时间 */
else if (icsk->icsk_user_timeout &&
(s32)(tcp_time_stamp - start_ts) > icsk->icsk_user_timeout)
goto abort; /* 最大探测次数设置为连接状态的重试次数 */
max_probes = sock_net(sk)->ipv4.sysctl_tcp_retries2;
/* 套接口即将关闭 */
if (sock_flag(sk, SOCK_DEAD)) {
/* 退避指数计算的超时时间< 最大时间(RTT),大于数据无法返回了 */
const bool alive = inet_csk_rto_backoff(icsk, TCP_RTO_MAX) < TCP_RTO_MAX; /* 获取在本端关闭tcp前重试次数上限 */
max_probes = tcp_orphan_retries(sk, alive); /* 超过了最大RTO时间或者退避指数达到了探测最大次数 */
if (!alive && icsk->icsk_backoff >= max_probes)
goto abort; /* 超过资源限制,关闭了连接,无需发送 */
if (tcp_out_of_resources(sk, true))
return;
} /* 探测次数超过了最大探测次数,错误处理,关闭连接 */
if (icsk->icsk_probes_out > max_probes) {
abort: tcp_write_err(sk);
} else {
/* Only send another probe if we didn't close things up. */
/* 发送探测 */
tcp_send_probe0(sk);
}
}

tcp_send_probe0调用tcp_write_wakeup发送探测段,并且根据发送结果来设定不同的退避指数,探测次数,下一次探测时间等,并重置定时器;

 /* A window probe timeout has occurred.  If window is not closed send
* a partial packet else a zero probe.
*/
void tcp_send_probe0(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
unsigned long probe_max;
int err; /* 发送探测报文 */
err = tcp_write_wakeup(sk, LINUX_MIB_TCPWINPROBE); /*
(1)如果存在发送出去未被确认的段,
要么被确认返回窗口,要么重传,无需额外构造探测包
(2)或者发送队列没有待发送的段,无数据需要发,
不关心窗口情况
则无需另外组织探测数据
*/
if (tp->packets_out || !tcp_send_head(sk)) {
/* Cancel probe timer, if it is not required. */
icsk->icsk_probes_out = ;
icsk->icsk_backoff = ;
return;
} /* 发送成功或者非本地拥塞导致的失败 */
if (err <= ) {
/* 退避指数未达到重试上限,递增 */
if (icsk->icsk_backoff < net->ipv4.sysctl_tcp_retries2)
icsk->icsk_backoff++;
/* 探测次数递增 */
icsk->icsk_probes_out++;
/* 探测时间设置为rto_max */
probe_max = TCP_RTO_MAX;
} else {
/* If packet was not sent due to local congestion,
* do not backoff and do not remember icsk_probes_out.
* Let local senders to fight for local resources.
*
* Use accumulated backoff yet.
*/
/* 本地拥塞发送失败 */ /* 设置探测次数 */
if (!icsk->icsk_probes_out)
icsk->icsk_probes_out = ; /* 设置探测时间为probe_interval */
probe_max = TCP_RESOURCE_PROBE_INTERVAL;
} /* 重置探测定时器 */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
tcp_probe0_when(sk, probe_max),
TCP_RTO_MAX);
}

TCP定时器 之 坚持定时器的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. TCP定时器 之 FIN_WAIT_2定时器

    当TCP主动关闭一端调用了close()来执行连接的完全关闭时会执行以下流程,本端发送FIN给对端,对端回复ACK,本端进入FIN_WAIT_2状态,此时只有对端发送了FIN,本端才会进入TIME_W ...

  7. TCP定时器 之 保活定时器

    在用户进程启用了保活定时器的情况下,如果连接超过空闲时间没有数据交互,则保活定时器超时,向对端发送保活探测包,若(1)收到回复则说明对端工作正常,重置定时器等下下次达到空闲时间:(2) 收到其他回复, ...

  8. tcp中的常见定时器

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

  9. tcp 保活定时器分析 & Fin_WAIT_2 定时器

    tcp keepalive定时器 http server 和client端需要防止"僵死"链接过多!也就是建立了tcp链接,但是没有报文交互, 或者client 由于主机突然掉电! ...

随机推荐

  1. sql server truncate语句

    truncate语句 --truncate table '表名' --这样就利用SQL语句清空了该数据表,而不保留日志

  2. ubuntu18.04系统安装及php7.2,apache2,mysql8,git,svn,composer,vs code 到安装 php 扩展配置php.ini 实现 laravel5.8 运行

    简介:记录自己从系统安装到环境配置完毕运行laravel的记录    • 下载ubuntu18.04桌面版        ○ ubuntu18.04中国官网 https://cn.ubuntu.com ...

  3. Axure(二)

    回顾1.Axure    动态面板        图片转换        画面滚动2.使用元件  -->  page box  盒子 width                 height   ...

  4. 借用jquery实现:使浏览器的“前进”按钮功能失效

    我借用jquery实现了这种效果,但并没有禁用掉浏览器本身的“前进”按钮,以下是代码,希望有用的朋友借鉴以下: $(function () { jQuery(window).bind("un ...

  5. EF报错:对一个或多个实体的验证失败(Entity Framework 强制转换失败数据异常处理方法)

    1.使用MVC和EF,在保存数据的时候报错:System.Data.Entity.Validation.DbEntityValidationException: 对一个或多个实体的验证失败.有关详细信 ...

  6. 免费数学神器Mathpix发布移动版了,一起来写更快的公式

    目录 1. 按 2. 下载地址 3. 介绍和使用 3.1. 介绍 3.2. 实际使用体验 1. 按 本文介绍的Mathpix可用于将手写的公式通过拍照或截图转成LaTeX 表达式. 写博客.记笔记最麻 ...

  7. Firefox 的User Agent 将移除 CPU 架构信息

    Mozilla 计划从 Firefox 的 User Agent(用户代理)和几个支持的 API 中移除 CPU 架构信息,以减少 Firefox 用户的“数字指纹”.Web 浏览器会自动向用户在应用 ...

  8. python-装饰(高阶函数)

    python-装饰(高阶函数) 高阶函数 1.把一个函数名当做实参传给另外一个函数(在不修改被装饰函数源代码) 2.返回值 中包含函数名 高阶函数实现1的功能 def bar(): print(&qu ...

  9. CentOS 7 中英文桌面安装步骤详细图解

    https://www.cnblogs.com/haoliyou/p/7694868.html

  10. spring+mybatis 多数据源的配置

    方式一: 参见博客https://www.cnblogs.com/AmbitiousMice/p/6027674.html 此种方式每次需要在调用dao的时候设置对应的数据源. 方式二: 直接在myb ...