TCP的定时器系列 — 零窗口探测定时器(有图有代码有真相!!!)

转载

主要内容:零窗口探测定时器的实现。

内核版本:3.15.2

我的博客:http://blog.csdn.net/zhangskd

 

出现以下情况时,TCP接收方的接收缓冲区将被塞满数据:

发送方的发送速度大于接收方的接收速度。

接收方的应用程序未能及时从接收缓冲区中读取数据。

 

当接收方的接收缓冲区满了以后,会把响应报文中的通告窗口字段置为0,从而阻止发送方的继续发送,

这就是TCP的流控制。当接收方的应用程序读取了接收缓冲区中的数据以后,接收方会发送一个ACK,通过

通告窗口字段告诉发送方自己又可以接收数据了,发送方收到这个ACK之后,就知道自己可以继续发送数据了。

 

Q:那么问题来了,当接收方的接收窗口重新打开之后,如果它发送的ACK丢失了,发送方还能得知这一消息吗?

A:答案是不能。正常的ACK报文不需要确认,因而也不会被重传,如果这个ACK丢失了,发送方将无法得知对端

的接收窗口已经打开了,也就不会继续发送数据。这样一来,会造成传输死锁,接收方等待对端发送数据包,而发送

方等待对端的ACK,直到连接超时关闭。

 

为了避免上述情况的发生,发送方实现了一个零窗口探测定时器,也叫做持续定时器:

当接收方的接收窗口为0时,每隔一段时间,发送方会主动发送探测包,通过迫使对端响应来得知其接收窗口有无打开。

这就是山不过来,我就过去:)

 

激活

 

(1) 发送数据包时

在发送数据包时,如果发送失败,会检查是否需要启动零窗口探测定时器。

tcp_rcv_established

    |--> tcp_data_snd_check

               |--> tcp_push_pending_frames

static inline void tcp_push_pending_frames(struct sock *sk)
{if (tcp_send_head(sk)) { /* 发送队列不为空 */struct tcp_sock *tp = tcp_sk(sk);__tcp_push_pending_frames(sk, tcp_current_mss(sk), tp->nonagle);}
}/* Push out any pending frames which were held back due to TCP_CORK* or attempt at coalescing tiny packets.* The socket must be locked by the caller.*/
void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss, int nonagle)
{/* If we are closed, the bytes will have to remain here.* In time closedown will finish, we empty the write queue and* all will be happy.*/if (unlikely(sk->sk_state == TCP_CLOSE))return;/* 如果发送失败 */if (tcp_write_xmit(sk, cur_mss, nonagle, 0, sk_gfp_atomic(sk, GFP_ATOMIC)))tcp_check_probe_timer(sk); /* 检查是否需要启用0窗口探测定时器*/
}

 

当网络中没有发送且未确认的数据包,且本端有待发送的数据包时,启动零窗口探测定时器。

为什么要有这两个限定条件呢?

如果网络中有发送且未确认的数据包,那这些包本身就可以作为探测包,对端的ACK即将到来。

如果没有待发送的数据包,那对端的接收窗口为不为0根本不需要考虑。

static inline void tcp_check_probe_timer(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);const struct inet_connection_sock *icsk = inet_csk(sk);/* 如果网络中没有发送且未确认的数据段,并且零窗口探测定时器尚未启动,*  则启用0窗口探测定时器。*/if (! tp->packets_out && ! icsk->icsk_pending)inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,icsk->icsk_rto, TCP_RTO_MAX);
}

 

(2) 接收到ACK时

tcp_ack()用于处理接收到的带有ACK标志的段,会检查是否要删除或重置零窗口探测定时器。

static int tcp_ack (struct sock *sk, const struct sk_buff *skb, int flag)
{...icsk->icsk_probes_out = 0; /* 清零探测次数,所以如果对端有响应ACK,实际上是没有次数限制的 */tp->rcv_tstamp = tcp_time_stamp; /* 记录最近接收到ACK的时间点,用于保活定时器 *//* 如果之前网络中没有发送且未确认的数据段 */if (! prior_packets) goto no_queue;...
no_queue:/* If data was DSACKed, see if we can undo a cwnd reduction. */if (flag & FLAG_DSACKING_ACK)tcp_fastretrans_alert(sk,acked, prior_unsacked, is_dupack, flag);/* If this ack opens up a zero window, clear backoff.* It was being used to time the probes, and is probably far higher than* it needs to be for normal retransmission.*//* 如果还有待发送的数据段,而之前网络中却没有发送且未确认的数据段,* 很可能是因为对端的接收窗口为0导致的,这时候便进行零窗口探测定时器的处理。*/if (tcp_send_head(sk)) /* 如果ACK打开了接收窗口,则删除零窗口探测定时器。否则根据退避指数,给予重置 */tcp_ack_probe(sk);
}

 

接收到一个ACK的时候,如果之前网络中没有发送且未确认的数据段,本端又有待发送的数据段,

说明可能遇到对端接收窗口为0的情况。

这个时候会根据此ACK是否打开了接收窗口来进行零窗口探测定时器的处理:

1. 如果此ACK打开接收窗口。此时对端的接收窗口不为0了,可以继续发送数据包。

    那么清除超时时间的退避指数,删除零窗口探测定时器。

2. 如果此ACK是接收方对零窗口探测报文的响应,且它的接收窗口依然为0。那么根据指数退避算法,

    重新设置零窗口探测定时器的下次超时时间,超时时间的设置和超时重传定时器的一样。

#define ICSK_TIME_PROBE0 3 /* Zero window probe timer */static void tcp_ack_probe(struct sock *sk)
{const struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);/* Was it a usable window open ?* 对端是否有足够的接收缓存,即我们能否发送一个包。*/if (! after(TCP_SKB_CB(tcp_send_head(sk))->end_seq, tcp_wnd_end(tp))) {icsk->icsk_backoff = 0; /* 清除退避指数 */inet_csk_clear_xmit_timer(sk, ICSK_TIME_PROBE0); /* 清除零窗口探测定时器*//* Socket must be waked up by subsequent tcp_data_snd_check().* This function is not for random using!*/} else { /* 否则根据退避指数重置零窗口探测定时器 */inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX), TCP_RTO_MAX);}
}/* 返回发送窗口的最后一个字节序号 */
/* Returns end sequence number of the receiver's advertised window */
static inline u32 tcp_wnd_end(const struct tcp_sock *tp)
{return tp->snd_una + tp->snd_wnd;
}

 

超时处理函数

 

icsk->icsk_retransmit_timer可同时作为:超时重传定时器、ER延迟定时器、PTO定时器,

还有零窗口探测定时器,它们的超时处理函数都为tcp_write_timer_handler(),在函数内则

根据超时事件icsk->icsk_pending来做区分。

 

具体来说,当网络中没有发送且未确认的数据段时,icsk->icsk_retransmit_timer才会用作零窗口探测定时器。

而其它三个定时器的使用场景则相反,只在网络中有发送且未确认的数据段时使用。  

和超时重传定时器一样,零窗口探测定时器也使用icsk->icsk_rto和退避指数来计算超时时间。

void tcp_write_timer_handler(struct sock *sk)
{struct inet_connection_sock *icsk = inet_csk(sk);int event;/* 如果连接处于CLOSED状态,或者没有定时器在计时 */if (sk->sk_state == TCP_CLOSE || !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_EARLY_RETRANS: /* ER延迟定时器触发的 */tcp_resume_early_retransmit(sk); /* 进行early retransmit */break;case ICSK_TIME_LOSS_PROBE: /* PTO定时器触发的 */tcp_send_loss_probe(sk); /* 发送TLP探测包 */break;case ICSK_TIME_RETRANS: /* 超时重传定时器触发的 */icsk->icsk_pending = 0;tcp_retransmit_timer(sk);break;case ICSK_TIME_PROBE0: /* 零窗口探测定时器触发的 */icsk->icsk_pending = 0;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;/* 如果网络中有发送且未确认的数据包,或者没有待发送的数据包。* 这个时候不需要使用零窗口探测定时器。前一种情况时已经有现成的探测包了,* 后一种情况中根本就不需要发送数据了。*/if (tp->packets_out || ! tcp_send_head(sk)) {icsk->icsk_probes_out = 0; /* 清零探测包的发送次数 */return;}/* icsk_probes_out is zeroed by incoming ACKs even if they advertise zero window.* Hence, connection is killed only if we received no ACKs for normal connection timeout.* It is not killed only because window stays zero for some time, window may be zero until* armageddon and even later. We are full accordance with RFCs, only probe timer combines* both retransmission timeout and probe timeout in one bottle.*/max_probes = sysctl_tcp_retries2; /* 当没有收到ACK时,运行发送探测包的最大次数,之后连接超时 */if (sock_flag(sk, SOCK_DEAD)) { /* 如果套接口即将关闭 */const int alive = ((icsk->icsk_rto << icsk->icsk_backoff) < TCP_RTO_MAX);max_probes = tcp_orphan_retries(sk, alive); /* 决定重传的次数 *//* 如果当前的孤儿socket数量超过tcp_max_orphans,或者内存不够时,关闭此连接 */if (tcp_out_of_resource(sk, alive || icsk->icsk_probes_out <= max_probes))return;}/* 如果发送出的探测报文的数目达到最大值,却依然没有收到对方的ACK时,关闭此连接 */if (icsk->icsk_probes_out > max_probes) { /* 实际上每次收到ACK后,icsk->icsk_probes_out都会被清零 */tcp_write_err(sk);} else {/* Only send another probe if we didn't close things up. */tcp_send_probe0(sk); /* 发送零窗口探测报文 */}
}

 

发送0 window探测报文和发送Keepalive探测报文用的是用一个函数tcp_write_wakeup():

1. 有新的数据段可供发送,且对端接收窗口还没被塞满。发送新的数据段,来作为探测包。

2. 没有新的数据段可供发送,或者对端的接收窗口满了。发送序号为snd_una - 1、长度为0的ACK包作为探测包。

 

和保活探测定时器不同,零窗口探测定时器总是使用第二种方法,因为此时对端的接收窗口为0。

所以会发送一个序号为snd_una - 1、长度为0的ACK包,对端收到此包后会发送一个ACK响应。

如此一来本端就能够知道对端的接收窗口是否打开了。

/* 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);int err;/* 发送一个序号为snd_una - 1,长度为0的ACK包作为零窗口探测报文 */err = tcp_write_wakeup(sk);/* 如果网络中有发送且未确认的数据包,或者没有待发送的数据包。* 这个时候不需要使用零窗口探测定时器。前一种情况时已经有现成的探测包了,* 后一种情况中根本就不需要发送数据了。check again 8)*/if (tp->packets_out || ! tcp_send_head(sk)) {/* Cancel probe timer, if it is not required. */icsk->icsk_probes_out = 0;icsk->icsk_backoff = 0;return;}/* err:0成功,-1失败 */if (err < = 0) {if (icsk->icsk_backoff < sysctl_tcp_retries2)icsk->icsk_backoff++; /* 退避指数 */icsk->icsk_probes_out++; /* 探测包的发送次数 */inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_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 = 1;/* 使零窗口探测定时器更快的超时 */inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0, min(icsk->icsk_rto << icsk->icsk->icsk_backoff, TCP_RESOURCE_PROBE_INTERVAL),TCP_RTO_MAX);}
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中XPATH操作xml,非常便捷

<?xml version"1.0" encoding"UTF-8"?> <MessageList><item type"1"><template_id value"p2ItJPj0taTTP4QRXP-z51nYuD3aDNhgvLOusWGY4p0"/><topcolor value"#173177"/><first value&quo…

【python】r+,w+ 全局变量

来源&#xff1a;http://www.educity.cn/wenda/352188.html r&#xff1a;可读可写&#xff0c;若文件不存在&#xff0c;报错w: 可读可写&#xff0c;若文件不存在&#xff0c;创建文本模式&#xff1a;遇换行符时根据操作系统不同自动转换换行符&#xff0c;比如读文件时遇\n…

网络:TCP通讯之 time_wait 状态

基于TCP协议的通讯流程1、TCP建立连接2、TCP断开连接3、TCP状态转换TCP状态解释&#xff1a; SYN-RECVD&#xff1a;再收到和发送一个连接请求后等待对方对连接请求的确认 ESTABLISHED&#xff1a;代表一个打开的连接 FIN-WAIT-1&#xff1a;等待远程TCP连接中断请求&#xff0…

linux下echo与time服务的程序实现

一、针对ECHO服务的TCP客户软件的实现 1.网络拓扑结构&#xff1a; 2.源码&#xff1a; 1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <stdarg.h>6 #include <sys/types.h>7 #include…

iOS UI-AlertView(警示框)和ActionSheet(选择框、操作表单)

1 #import "ViewController.h"2 3 interface ViewController ()<UIAlertViewDelegate,UIActionSheetDelegate>4 5 end6 7 implementation ViewController8 9 #pragma mark - 生命周期10 - (void)viewDidLoad {11 [super viewDidLoad];12 // 创建展示Al…

Linux高性能服务器编程:进程池和线程池原理及应用(有图有代码有真相!!!)

一、问题引入 在前面编写多进程、多线程服务器时通过动态创建子进程和子线程来实现并发服务器&#xff0c;这样做有以下缺点&#xff1a; 1&#xff09;动态创建进程、线程将会比较耗费时间&#xff0c;将导致较慢的客户响应。 2&#xff09;动态创建的子进程只为一个客户服…

Linux:多进程、多线程服务器的实现解析(有图有代码有真相!!!)

一、问题引入 阻塞型的网络编程接口 几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv()等接口开始的。使用这些接口可以很方便的构建服务器 /客户机的模型。 我们假设希望建立一个简单的服务器程序&#xff0c;实现向单个客户机提供类似于“一问一答”的…

数据结构:神奇的B树实现解析(有图有代码有真相!!!)

一、B树引入 二叉搜索树、平衡二叉树、红黑树都是动态查找树&#xff0c;典型的二叉搜索树结构&#xff0c;查找的时间复杂度和树的高度相关O(log2N)。 1&#xff09;数据杂乱无章-------线性查找--O&#xff08;n&#xff09; 2&#xff09;数据有序-------二分查找 ---O(lo…

Linux:dup/dup2 文件描述符重定向函数(有图有代码有真相!!!)

一、dup/dup2 有时我们希望把标准输入重定向到一个文件&#xff0c;或者把标准输出重定向到一个网络连接。系统调用dup和dup2能够复制文件描述符。dup返回新的文件文件描述符&#xff08;没有用的文件描述符最小的编号&#xff09;。 dup2可以让用户指定返回的文件描述符的值…

Linux:I/O多路转接之select(有图有代码有真相!!!)

一、select引入 一次 I/O 分为两个部分&#xff1a;1&#xff09;等待数据就绪 2&#xff09;进行数据转移 1、select 原理&#xff1a; select的原理就是减少等待数据就绪的比重&#xff0c;巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠&#xff0c;有资…

Linux: I/O多路转接之poll(有图有代码有真相!!!)

一、poll()函数解析 不同与select使⽤用三个位图来表⽰示三个fdset的⽅方式&#xff0c;poll使⽤用⼀一个 pollfd的指针实现。pollfd结构包含了要监视的event和发⽣生的event&#xff0c; 不再使⽤用select“参数-值”传递的⽅方式。同时&#xff0c;pollfd并没有最⼤大数量限…

kalilinux装到u盘上的弊端_付费下载的歌曲,竟然无法在汽车上播放!原因在这里...

『使用某音乐播放器下载了周杰伦的110首歌曲&#xff0c;其中106首是kgm格式&#xff0c;4首mp3格式&#xff0c;装到U盘后&#xff0c;在其它设备播放只有4首mp3格式的可以播放&#xff0c;其它的均无法播放&#xff0c;请问该如何处理&#xff1f;』网友留言截图这是一位网友…

iconsvg image怎么变为path_昆凌是怎么收服天王周杰伦的?这几招太高明了

周杰伦和昆凌又出来撒狗粮了&#xff01;就在前两天(6月2日)&#xff0c;在参加郎朗的婚礼时&#xff0c;#周杰伦搂昆凌看烟花#的消息悄悄上了热搜。视频中&#xff0c;两人并肩站立&#xff0c;一起欣赏着窗外的美景。周杰伦时不时在昆凌的耳边私语几句&#xff0c;看起来很是…

Linux: I/O多路转接之epoll(有图有代码有真相!!!)

一、基本概念 epoll是Linux内核为处理大批量文件描述符而作了改进的poll&#xff0c;是Linux下多路复用IO接口select/poll的增强版本&#xff0c;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。 另一点原因就是获取事件的时候&#xff0c;它无须遍历整…

ewebeditor未授权:功能被禁用请先配置授权_SteamPY新功能——外区账号礼物自动领取...

自从PY平台增加了外区代购后发现许多玩家在购买礼物时常会发生收到礼物后准备点击入库时弹出地区不可用的提示这个问题在Steam外区账号一直频繁发生究其因在于Steam账号登录时的IP问题遇到该问题切勿拒收礼物&#xff01;通过Steam客户端清理登录授权注销退出后再次使用账号对应…

Linux: shell 中命令代换 $() 和 ``(有图有代码有真相!!!)

一、命令代换&#xff08;命令替换&#xff09; 由 或 $() 括起来的也是一条命令&#xff0c;shell先执行该命令&#xff0c;再将结果立刻代换到当前命令行中。 简单例子&#xff1a; DATEdate echo $DATE DATE$(date) echo $DATE 执行结果&#xff1a; 二、优缺点&#x…

精雕道路怎么遍弧形_【养护技术】道路“创可贴”——沥青冷补料 六大优势助力道路养护...

点击上面蓝字关注我们微信号&#xff1a;xzgsgl随着城市精细化管理目标不断提高&#xff0c;市政道路养护修补的要求也越来越高。不但对修补的外观、质量有了更高的标准&#xff0c;对修复时限也提出了一定要求&#xff0c;这就要求我们的养护单位快速、优质地完成道路修补任务…

单耳蓝牙耳机怎么连接_蓝牙耳机怎么挑选?推荐性价比高的蓝牙耳机

随着手机逐渐取消了耳机孔&#xff0c;越来越多的人们开始使用上了蓝牙耳机。在当今这个飞速发展的时代&#xff0c;蓝牙耳机无疑成为了新时代的宠儿。无论是上班族还是当代大学生等年轻化群体&#xff0c;耳机的第一选择都是蓝牙耳机。但是面对市面上如此多的蓝牙耳机&#xf…

Linux: shell命令 eval (有图有代码有真相!!!)

一、eval 命令定义 shell中的eval命令将会首先扫描命令行进行所有的替换&#xff0c;然后再执行命令。该命令使用于那些一次扫描无法实现其功能的变量。 该命令对变量进行两次扫描。这些需要进行两次扫描的变量有时候被称为复杂变量。不过这些变量本身并不复杂。eval 命令也可…

Linux:shell脚本命令: /dev/null 21 的理解

1、可以将/dev/null看作"黑洞". 它非常等价于一个只写文件. 所有写入它的内容都会永远丢失. 而尝试从它那儿读取内容则什么也读不到. 然而, /dev/null对命令行和脚本都非常的有用. 禁止标准输出. 1 cat $filename >/dev/null # 文件内容丢失&#xff0c;而不…