app开发网站建设培训班,外贸网站如何换域名,名字logo设计在线生成免费,织梦做网站要多长时间转载 主要内容#xff1a;零窗口探测定时器的实现。 内核版本#xff1a;3.15.2 我的博客#xff1a;http://blog.csdn.net/zhangskd 出现以下情况时#xff0c;TCP接收方的接收缓冲区将被塞满数据#xff1a; 发送方的发送速度大于接收方的接收速度。 接收方的应用程序未… 转载 主要内容零窗口探测定时器的实现。 内核版本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 receivers 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 didnt 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;}/* err0成功-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);}
}