9.6.1 Why
数据发送方收到接收方的通告窗口为0时,就不能再发送数据,一直等到对方发送窗口更新为止。但对端发送的窗口更新报文可能会丢失,如果发送方只是等待的话会导致数据传输会一直停滞,最后连接会被断开。这时坚持定时器闪亮登场!数据发送方可以设置坚持定时器定时发送1个探测报文,对端收到后会对这个报文发送ACK报文,这样发送方就能及时得知窗口更新事件了。一旦窗口非0则数据传输就可以恢复正常的数据传输。
9.6.2 When
设置坚持定时器的时机有两个:
(1)TCP使用__tcp_push_pending_frames发送数据时:
2032 void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
2033 int nonagle)
2034 {
2035 /* If we are closed, the bytes will have to remain here.
2036 * In time closedown will finish, we empty the write queue and
2037 * all will be happy.
2038 */
2039 if (unlikely(sk->sk_state == TCP_CLOSE))
2040 return;
2041
2042 if (tcp_write_xmit(sk, cur_mss, nonagle, 0,
2043 sk_gfp_atomic(sk, GFP_ATOMIC))) //判断为真意味着所有发送出去的数据都已经被确认且发送队列中还有数据未发送,即可能是因为窗口太小无法发送
2044 tcp_check_probe_timer(sk); //设置坚持定时器
2045 }
tcp_check_probe_timer函数: 973 static inline void tcp_check_probe_timer(struct sock *sk)
974 {
975 const struct tcp_sock *tp = tcp_sk(sk);
976 const struct inet_connection_sock *icsk = inet_csk(sk);
977
978 if (!tp->packets_out && !icsk->icsk_pending) //所有发送出去的数据都已经被确认且未设置坚持定时器、重传定时器、ER定时器和TLP定时器
979 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
980 icsk->icsk_rto, TCP_RTO_MAX);
981 }
(2)收到ACK时: 3168 static void tcp_ack_probe(struct sock *sk)
3169 {
3170 const struct tcp_sock *tp = tcp_sk(sk);
3171 struct inet_connection_sock *icsk = inet_csk(sk);
3172
3173 /* Was it a usable window open? */
3174
3175 if (!after(TCP_SKB_CB(tcp_send_head(sk))->end_seq, tcp_wnd_end(tp))) { //当前窗口可以容纳下一个要发送的包
3176 icsk->icsk_backoff = 0;
3177 inet_csk_clear_xmit_timer(sk, ICSK_TIME_PROBE0);//清除坚持定时器
3178 /* Socket must be waked up by subsequent tcp_data_snd_check().
3179 * This function is not for random using!
3180 */
3181 } else {
3182 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
3183 min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX),
3184 TCP_RTO_MAX);
3185 }
3186 }
...
3325 static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
3326 {
...
3365 prior_fackets = tp->fackets_out;
...
3409 if (!prior_packets)//所有发送出去的数据都已经被确认
3410 goto no_queue;
...
3443 no_queue:
...
3452 if (tcp_send_head(sk))//发送队列中还有数据未发送
3453 tcp_ack_probe(sk);//设置坚持定时器
设置坚持定时器的条件是:所有发送出去的数据都已经被确认且发送队列中还有数据未发送(这时不会设置重传定时器、ER定时器和TLP定时器)。数据未发送的原因可能是发送窗口过小。
清除设置坚持定时器的条件是:
(1)发送窗口增大到能够允许发送至少一个报文
(2)安装了重传定时器、ER定时器或TLP定时器
坚持定时器的超时时间由RTO决定。
9.6.3 What
坚持定时器的超时函数为tcp_probe_timer:
262 static void tcp_probe_timer(struct sock *sk)
263 {
264 struct inet_connection_sock *icsk = inet_csk(sk);
265 struct tcp_sock *tp = tcp_sk(sk);
266 int max_probes;
267
268 if (tp->packets_out || !tcp_send_head(sk)) { //有发送出去的数据未确认或发送队列为空
269 icsk->icsk_probes_out = 0;
270 return;
271 }
...
288 max_probes = sysctl_tcp_retries2;
289
290 if (sock_flag(sk, SOCK_DEAD)) {//当前socket是孤儿socket
291 const int alive = ((icsk->icsk_rto << icsk->icsk_backoff) < TCP_RTO_MAX);
292
293 max_probes = tcp_orphan_retries(sk, alive);
294
295 if (tcp_out_of_resources(sk, alive || icsk->icsk_probes_out <= max_probes)) //孤儿socket占用资源过多
296 return;
297 }
298
299 if (icsk->icsk_probes_out > max_probes) { //探测次数超出上限
300 tcp_write_err(sk);
301 } else {
302 /* Only send another probe if we didn't close things up. */
303 tcp_send_probe0(sk); //发送探测报文
304 }
305 }
tcp_send_probe0会发送探测报文: 3099 int tcp_write_wakeup(struct sock *sk)
3100 {
3101 struct tcp_sock *tp = tcp_sk(sk);
3102 struct sk_buff *skb;
3103
3104 if (sk->sk_state == TCP_CLOSE)
3105 return -1;
3106
3107 if ((skb = tcp_send_head(sk)) != NULL && //有数据尚未发送
3108 before(TCP_SKB_CB(skb)->seq, tcp_wnd_end(tp))) { //当前窗口允许发送至少1字节的新数据
3109 int err;
3110 unsigned int mss = tcp_current_mss(sk);
3111 unsigned int seg_size = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
3112
3113 if (before(tp->pushed_seq, TCP_SKB_CB(skb)->end_seq))
3114 tp->pushed_seq = TCP_SKB_CB(skb)->end_seq;
3115
3116 /* We are probing the opening of a window
3117 * but the window size is != 0
3118 * must have been a result SWS avoidance ( sender )
3119 */
3120 if (seg_size < TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq ||
3121 skb->len > mss) { //数据段过大
3122 seg_size = min(seg_size, mss);
3123 TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;
3124 if (tcp_fragment(sk, skb, seg_size, mss))
3125 return -1;
3126 } else if (!tcp_skb_pcount(skb))
3127 tcp_set_skb_tso_segs(sk, skb, mss);
3128
3129 TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_PSH;
3130 TCP_SKB_CB(skb)->when = tcp_time_stamp;
3131 err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC); //发送新数据作为探测报文
3132 if (!err) //发送成功
3133 tcp_event_new_data_sent(sk, skb); //处理发送了新数据的事件
3134 return err;
3135 } else { //没有数据要发送或当前发送窗口不允许发送新数据
3136 if (between(tp->snd_up, tp->snd_una + 1, tp->snd_una + 0xFFFF)) //有紧急数据未确认且在窗口之内,这时一定有数据要发送
3137 tcp_xmit_probe_skb(sk, 1); //发送一个使用重复序列号的ACK
3138 return tcp_xmit_probe_skb(sk, 0); //发送一个使用旧序列号的ACK
3139 }
3140 }
3141
3142 /* A window probe timeout has occurred. If window is not closed send
3143 * a partial packet else a zero probe.
3144 */
3145 void tcp_send_probe0(struct sock *sk)
3146 {
3147 struct inet_connection_sock *icsk = inet_csk(sk);
3148 struct tcp_sock *tp = tcp_sk(sk);
3149 int err;
3150
3151 err = tcp_write_wakeup(sk); //发送探测报文
3152
3153 if (tp->packets_out || !tcp_send_head(sk)) { //有发送出去的数据未确认或发送队列为空
3154 /* Cancel probe timer, if it is not required. */
3155 icsk->icsk_probes_out = 0;
3156 icsk->icsk_backoff = 0;
3157 return;
3158 }
3159
3160 if (err <= 0) {
3161 if (icsk->icsk_backoff < sysctl_tcp_retries2)
3162 icsk->icsk_backoff++;
3163 icsk->icsk_probes_out++;
3164 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
3165 min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX),
3166 TCP_RTO_MAX);
3167 } else { //包在底层由于队列拥塞没有发送出去
3168 /* If packet was not sent due to local congestion,
3169 * do not backoff and do not remember icsk_probes_out.
3170 * Let local senders to fight for local resources.
3171 *
3172 * Use accumulated backoff yet.
3173 */
3174 if (!icsk->icsk_probes_out)
3175 icsk->icsk_probes_out = 1;
3176 inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
3177 min(icsk->icsk_rto << icsk->icsk_backoff,
3178 TCP_RESOURCE_PROBE_INTERVAL),
3179 TCP_RTO_MAX);
3180 }
3181 }
tcp_xmit_probe_skb函数用于发送一个无数据的报文:3068 static int tcp_xmit_probe_skb(struct sock *sk, int urgent)
3069 {
3070 struct tcp_sock *tp = tcp_sk(sk);
3071 struct sk_buff *skb;
3072
3073 /* We don't queue it, tcp_transmit_skb() sets ownership. */
3074 skb = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
3075 if (skb == NULL)
3076 return -1;
3077
3078 /* Reserve space for headers and set control bits. */
3079 skb_reserve(skb, MAX_TCP_HEADER);
3080 /* Use a previous sequence. This should cause the other
3081 * end to send an ack. Don't queue or clone SKB, just
3082 * send it.
3083 */
3084 tcp_init_nondata_skb(skb, tp->snd_una - !urgent, TCPHDR_ACK);
3085 TCP_SKB_CB(skb)->when = tcp_time_stamp;
3086 return tcp_transmit_skb(sk, skb, 0, GFP_ATOMIC);
3087 }
如果探测次数超出限制或内存紧张,坚持定时器会断开连接;否则,发送探测报文,然后重设坚持定时器。发送探测报文时如果发送窗口允许发送至少一字节,则发送一个新的报文段;否则发送一个seq比较旧的非法ACK,这样对端收到后会丢弃之并发送ACK报文(如果有紧急数据未确认则发送一个seq最旧但合法的ACK,why?)。
总之,坚持定时器发送探测报文并期望对端能对探测报文发送ACK,这样TCP就能得到最新的窗口信息。一旦窗口增加到可以发送数据,则正常的数据交互就可以尽快恢复。