转载

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

内核版本: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);}
}

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

  1. TCP的定时器系列 — 保活定时器(有图有代码有真相!!!)

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

  2. TCP的定时器系列 — 超时重传定时器(有图有代码有真相!!!)

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

  3. STM32F103系列的单片机定时器

    STM32F103系列的单片机一共有11个定时器,且所有定时器的技数频率均为72MHZ,其中: 2个高级定时器 4个普通定时器 2个基本定时器 2个看门狗定时器 1个系统嘀嗒定时器 出去看门狗定时器和 ...

  4. 第二代ANX74xx系列10 Gbps USB 3.2单芯片重定时器系列通过集成更多功能和性能,巩固硅谷数模在USB-C及部署在USB-C 上的DisplayPort方面的领先优势

    ANX74xx符合USB Gen 1 (5G)和Gen 2 (10G)的USB中继器规范要求,且基于市场验证的架构构建 加州圣克拉拉--(美国商业资讯)--硅谷数模半导体公司(Analogix Sem ...

  5. c++ 定时器_【话说定时器系列】之十:PWM输入模式测量脉宽及占空比实验

    STM32定时器是 ST MCU 内部最基础且常用的外设,实际应用尤为普遍.去年,电堂推出了<STM32 TIMER基础及常规应用介绍>,为大家梳理了 STM32 TIMER 的庞大内容, ...

  6. 恒烁M0+系列CX32L003单片机定时器控制LED亮灭

    定时器是单片机最重要的一个模块,有了定时器就可以做任何需要的功能. 本例采用通用定时器2来实现基本的定时功能--控制灯的亮灭,时间间隔设置成1S. 定时器需要设置2部分:初始化定时器.中断优先级.需要 ...

  7. python定时器 循环_Python系列之循环定时器

    近期在学习并使用Python开发一些小工具,在这里记录方便回忆,也与各位开始走上这条路的朋友共勉,如有不正确希望指正,谢谢! 开始使用定时器时,度娘了下有没好的例子,本人比较懒,希望能直接使用.确实找 ...

  8. 或许,这是你见过最全的TCP+UDP图解系列

    或许,这是你见过最全的TCP+UDP图解系列 01 图解TCP TCP首部 流量控制 拥塞控制 三次握手,四次挥手 tcp 怎样保证数据正确性? 流量控制是为了让接收方能来得及接收,而拥塞控制是为了降 ...

  9. ajax定时器怎么写,js定时器怎么写?就是在特定时间执行某段程序

    js定时器怎么写?就是在特定时间执行某段程序 复制代码 代码如下: $(function(){ var handler = function(){ } var timer = setInterval( ...

最新文章

  1. LIVE 预告 | 快手宋洋:千亿特征,万亿参数,快手推荐精排模型的发展史
  2. php修改http header,php header函数的常用http头设置
  3. FATE 集群部署 step1
  4. 如何记录网内用户访问了哪些网站
  5. 帮助创建未来的 .NET 客户端开发
  6. cadence安装完怎么打开_Linux 环境下Vivado与Cadence仿真工具联合仿真环境的搭建
  7. Java学习小程序(9)冒泡排序算法实现
  8. 为了证明自己有多能喝,理工科的学生竟做出这种事
  9. TV Distortion Bundle mac - AE画面像素破损信号干扰失真插件
  10. 模块_time模块/copy模块/os模块
  11. php5.5 下载安装,【图片】PHP5.2、5.3、5.4、5.5、5.6的php memcache dll扩展下载和安装【洄溪吧】_百度贴吧...
  12. 511遇见易语言大漠模块制作教程找图FindPic
  13. JAVA GUI创作简易记牌器
  14. 微信云控源码帮您快速复制营销
  15. 小技巧:两种方式快速实现平滑涂鸦画板
  16. 李大仁是真的爱程又青
  17. python从字符串中提取指定的内容
  18. Excel表格模板打包下载┆收集了各类各行业Excel表格、word模板
  19. 这世上没有末路,你从不曾孤独
  20. 蘑菇街php面试,蘑菇街面试

热门文章

  1. Liunx安装gogs,mysql,jdk,tomcat等常用软件
  2. centos7如何安装cloud-init
  3. siwft初学(一)
  4. 【Spring学习笔记-MVC-17】Spring MVC之拦截器
  5. Java基础-Java中的内存分配与回收机制
  6. 打开cmd窗口新技巧get
  7. 一个10年SEO工作者的35个SEO经验
  8. Servlet与JSP间的传值问题
  9. Android 弹出有确认按键的对话
  10. ASP.NET AJAX - Timer控件之摆放位置的影响