TCP定时器 之 坚持定时器
坚持定时器在接收方通告接收窗口为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定时器 之 坚持定时器的更多相关文章
- 动手学习TCP:4种定时器
上一篇中介绍了TCP数据传输中涉及的一些基本知识点.本文让我们看看TCP中的4种定时器. TCP定时器 对于每个TCP连接,TCP管理4个不同的定时器,下面看看对4种定时器的简单介绍. 重传定时器使用 ...
- 【网络协议】TCP中的四大定时器
前言 对于每个TCP连接,TCP一般要管理4个不同的定时器:重传定时器.坚持定时器.保活定时器.2MSL定时器. 重传定时器 非常明显重传定时器是用来计算TCP报文段的超时重传时间的(至于超时重传时间 ...
- 14.TCP的坚持定时器和保活定时器
一.坚持定时器 1.坚持定时器的由来 TCP通过让接收方指明希望从发送方接受的窗口大小来进行流量控制.设置窗口大小为0可以组织发送方传送数据,直至窗口变为非0为止. ...
- TCP的定时器系列 — 保活定时器
主要内容:保活定时器的实现,TCP_USER_TIMEOUT选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 原理 HTTP有Keepaliv ...
- TCP的四种定时器简单记录
TCP管理的4个不同的定时器: 1.重传定时器:用于当希望收到另一端的确认. 2.坚持定时器:使窗口大小信息保持不断流动. 3.保活定时器:检测TCP空闲连接的另一端何时崩溃或重启. 4.2MSL定时 ...
- TCP定时器 之 FIN_WAIT_2定时器
当TCP主动关闭一端调用了close()来执行连接的完全关闭时会执行以下流程,本端发送FIN给对端,对端回复ACK,本端进入FIN_WAIT_2状态,此时只有对端发送了FIN,本端才会进入TIME_W ...
- TCP定时器 之 保活定时器
在用户进程启用了保活定时器的情况下,如果连接超过空闲时间没有数据交互,则保活定时器超时,向对端发送保活探测包,若(1)收到回复则说明对端工作正常,重置定时器等下下次达到空闲时间:(2) 收到其他回复, ...
- tcp中的常见定时器
(1)超时重传定时器tcp的靠谱特性,通过确认机制,保证每一个包都被对方收到,那么什么时候需要重传呢?就是靠这个超时重传定时器,每次发送报文前都启动这个定时器,如果定时器超时之前收到了应答则关闭定时器 ...
- tcp 保活定时器分析 & Fin_WAIT_2 定时器
tcp keepalive定时器 http server 和client端需要防止"僵死"链接过多!也就是建立了tcp链接,但是没有报文交互, 或者client 由于主机突然掉电! ...
随机推荐
- spring boot 发布自动生成svn版本号
通过Jenkins构建发布spring boot项目时,常常有需求,需要把Svn的版本号更新到项目的版本上,通过有两种解决方案: 1. 通过shell命令对配置文件中的指定字符进行替换, 如: 配置文 ...
- 05 Django之模型层---单表操作
一 ORM简介 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人 ...
- PHP高级进阶之路
一:常见模式与框架 学习PHP技术体系,设计模式,流行的框架 常见的设计模式,编码必备 Laravel.ThinkPHP开发必不可少的最新框架 YII.Symfony4.1核心源码剖析 二:微服务架构 ...
- python 模块使用
模块使用 定义:模块就像一个工具包一样,里面有很多工具(函数.类),使用时需要通过import导入. 分类: 标准库:random.sys.os.time 第三方:就是好人已经写好的特定功能的模块,你 ...
- vue项目-axios封装、easy-mock使用
vue全家桶概括下来就是 项目构建工具(vue-cli) 路由(vue-router) 状态管理(vuex) http请求工具 vue有自己的http请求工具插件vue-resource,但是vue2 ...
- ubuntu终端安装ss
大概就是这样
- maven 依赖包找不到 (转)
1,手动添加jar包 例: maven在集成Oracle驱动的时候从远程仓库下载不下来ojdbc14 报missing artifact com.oracle:ojdbc14:jar:10.2.0.3 ...
- python、mysql三-1:存储引擎
一 什么是存储引擎 mysql中建立的库===>文件夹 库中建立的表===>文件 现实生活中我们用来存储数据的文件有不同的类型,每种文件类型对应各自不同的处理机制:比如处理文本用txt类型 ...
- DockerScan:Docker安全分析&测试工具
DockerScan:Docker安全分析&测试工具 今天给大家介绍的是一款名叫DockerScan的工具,我们可以用它来对Docker进行安全分析或者安全测试. 项目主页 http://gi ...
- Python package钓鱼
Python package钓鱼 一.概述 在收录该文之后,知道创宇404安全实验室对该文中所提到的攻击方式进行跟进.整理分析原作者公布的钓鱼数据.值得一提的是,在跟进的过程中,我们发现了新的钓鱼 ...