9.6 坚持(Persist)定时器

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就能得到最新的窗口信息。一旦窗口增加到可以发送数据,则正常的数据交互就可以尽快恢复。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章