tcp_ack

tcp_ack

前言

说明一些 tcp_sock 的成员的意义:

  • snd_una: 第一个希望收到的确认序号.
  • snd_nxt: 下一个要发送的第一个数据
  • rcv_wup: 第一个还没有发送的 ack 确认数据
  • rec_nxt: 下一个要接收的数据

tcp_ack

/* This routine deals with incoming acks, but not outgoing ones. */
static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{
         struct inet_connection_sock *icsk = inet_csk(sk);   // 获得连接sock
         struct tcp_sock *tp = tcp_sk(sk);   // 获得tcp_sock
         u32 prior_snd_una = tp->snd_una;    // 获得未发送确认的序号
         u32 ack_seq = TCP_SKB_CB(skb)->seq; // 获得数据序号
         u32 ack = TCP_SKB_CB(skb)->ack_seq; // 获得ack序号(用于确认的序号)
         u32 prior_in_flight;
         u32 prior_fackets;
         int prior_packets;
         int frto_cwnd = 0;

         /* If the ack is newer than sent or older than previous acks
          * then we can probably ignore it.
          */
         if (after(ack, tp->snd_nxt))    // 如果确认序号比我还下一个准备发送的序号还要大,即确认了我们尚未发送的数据,那么显然不合理
                 goto uninteresting_ack; // 没有意义的ACK

         if (before(ack, prior_snd_una)) // 如果ack确认比我期望的确认序号小,那么可能是以前老的ack,丢弃!!!
                 goto old_ack;           // 老的ack

         if (after(ack, prior_snd_una))  // 如果ack确认比我期望的第一个ack要大,但是经过上面我们还知道没有超过我没有发送的数据序号,范围
                 flag |= FLAG_SND_UNA_ADVANCED;  // 那么设置标识~

         if (sysctl_tcp_abc) { // 是否设置了tcp_abc,若有则我们不需要对每个ack确认都要拥塞避免,所以我们需要计算已经ack(确认)的字节数。
                 if (icsk->icsk_ca_state < TCP_CA_CWR)
                         tp->bytes_acked += ack - prior_snd_una;  // 已经(确定)ack的字节数增大了( ack - prior_snd_una )大小
                 else if (icsk->icsk_ca_state == TCP_CA_Loss)
                         /* we assume just one segment left network */
                         tp->bytes_acked += min(ack - prior_snd_una,
                                                tp->mss_cache);
         }

         prior_fackets = tp->fackets_out;              // 得到fack的数据包的字节数
         prior_in_flight = tcp_packets_in_flight(tp);  // 计算还在传输的数据段的字节数,下面会说手这个函数!( 1 )

         if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) { // 如果不是“慢路径” && ack确认比其需要的第一个大(正确的确认序号)
                 /* Window is constant, pure forward advance.
                  * No more checks are required.
                  * Note, we use the fact that SND.UNA>=SND.WL2.
                  */
                 tcp_update_wl(tp, ack, ack_seq);   // 需要更新sock中的snd_wl1字段:tp->snd_wl1 = ack_seq;( 记录造成发送窗口更新的第一个数据 )
                 tp->snd_una = ack;                 // snd_una更新为已经确认的序列号!下一次期待从这里开始的确认!!!
                 flag |= FLAG_WIN_UPDATE;           // 窗口更新标识

                 tcp_ca_event(sk, CA_EVENT_FAST_ACK);  // 重要函数!!!进入拥塞操作!这个函数最后看,这里处理的是“正常的ACK”事件(999999)

                 NET_INC_STATS_BH(LINUX_MIB_TCPHPACKS);
         } else {
                 if (ack_seq != TCP_SKB_CB(skb)->end_seq) // 如果不相等,那么说明还是带有数据一起的~不仅仅是一个ACK的包
                         flag |= FLAG_DATA;   // 说明还是有数据的~
                 else
                         NET_INC_STATS_BH(LINUX_MIB_TCPPUREACKS); // 否则仅仅是ACK的包

                 flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);  // 下面需要更新发送窗口~(2)

                 if (TCP_SKB_CB(skb)->sacked)  // 然后判断是否有sack段,有的话,我们进入sack段的处理。
                         flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una); // ~~~~~处理SACK(选择确认),以后单独解释

                 if (TCP_ECN_rcv_ecn_echo(tp, tcp_hdr(skb))) // 判断是否有ecn标记,如果有的话,设置ecn标记。
                         flag |= FLAG_ECE;                   // ECE 也是用于判断是否阻塞情况

                 tcp_ca_event(sk, CA_EVENT_SLOW_ACK); // 重要函数!!!进入拥塞操作!这个函数最后看,这里处理“其他ACK”事件(999999)
         }

         /* We passed data and got it acked, remove any soft error
          * log. Something worked...
          */
         sk->sk_err_soft = 0;
         tp->rcv_tstamp = tcp_time_stamp;
         prior_packets = tp->packets_out;  // 获得发出去没有收到确认的包数量
         if (!prior_packets) // 如果为0,则可能是0窗口探测包
                 goto no_queue;

         /* See if we can take anything off of the retransmit queue. */
         flag |= tcp_clean_rtx_queue(sk, prior_fackets);  // 清理重传队列中的已经确认的数据段。(3)

         if (tp->frto_counter)  // 处理F-RTO (4)
                 frto_cwnd = tcp_process_frto(sk, flag);   // 处理超时重传,暂时先不多说
         /* Guarantee sacktag reordering detection against wrap-arounds */
         if (before(tp->frto_highmark, tp->snd_una))
                 tp->frto_highmark = 0;

         if (tcp_ack_is_dubious(sk, flag)) {     // 判断ack是否可疑,其实本质就是判断可不可以增大拥塞窗口,下面会有详细解释(5)
                 /* Advance CWND, if state allows this. */
                 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd && // 如果是数据确认包
                     tcp_may_raise_cwnd(sk, flag))  // 检测flag以及是否需要update拥塞窗口的大小!!!(6)--->被怀疑也有可能增大窗口哦~~~
                         tcp_cong_avoid(sk, ack, prior_in_flight);   // 为真则更新拥塞窗口,拥塞避免算法(7)--->如果允许增大窗口,那么拥塞算法处理窗口
                 tcp_fastretrans_alert(sk, prior_packets - tp->packets_out, // 这里进入拥塞状态的处理,非常重要的函数(对于拥塞处理来说)!!!(8)
                                       flag);   // 处理完窗口变化之后,进入快速重传处理!!!
         } else {  // 没有被怀疑(说明是正常的确认ACK)
                 if ((flag & FLAG_DATA_ACKED) && !frto_cwnd)
                         tcp_cong_avoid(sk, ack, prior_in_flight);  // 如果没有被怀疑,直接拥塞算法处理窗口变化
         }

         if ((flag & FLAG_FORWARD_PROGRESS) || !(flag & FLAG_NOT_DUP))
                 dst_confirm(sk->sk_dst_cache);

         return 1;

no_queue:
         icsk->icsk_probes_out = 0;

         /* 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.
          */
         if (tcp_send_head(sk)) // 这里判断发送缓冲区是否为空,如果不为空,则进入判断需要重启keepalive定时器还是关闭定时器
                 tcp_ack_probe(sk);   // 处理定时器函数~(暂时不看)
         return 1;

old_ack:
         if (TCP_SKB_CB(skb)->sacked) // 如果有sack标识
                 tcp_sacktag_write_queue(sk, skb, prior_snd_una); // 进入sack段处理(暂时不看)

uninteresting_ack:
         SOCK_DEBUG(sk, "Ack %u out of %u:%u\n", ack, tp->snd_una, tp->snd_nxt);// 没有意义的包~
         return 0;
}

tcp_ack_update_window

static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack,
                                  u32 ack_seq)
{
         struct tcp_sock *tp = tcp_sk(sk);             // 获得tcp_sock
         int flag = 0;
         u32 nwin = ntohs(tcp_hdr(skb)->window);       // 获得skb发送方的可以接收的窗口值

         if (likely(!tcp_hdr(skb)->syn))               // 如果不是建立连接时候,即是普通传递数据时候,窗口缩放
                 nwin <<= tp->rx_opt.snd_wscale;       // 接收方要求对窗口进行缩放
         // 下面正式更新窗口
         if (tcp_may_update_window(tp, ack, ack_seq, nwin)) { // 可能需要更新窗口大小,在函数tcp_may_update_window中实际处理(1)
                 flag |= FLAG_WIN_UPDATE;                     // 窗口更新成功
                 tcp_update_wl(tp, ack, ack_seq);       // 更新snd_wl1

                 if (tp->snd_wnd != nwin) {  // 如果发送窗口!=缩放后的新窗口(注意skb发送方的接收窗口和本tp的发送窗口应该一致)
                         tp->snd_wnd = nwin; // 改变窗口值

                         /* Note, it is the only place, where
                          * fast path is recovered for sending TCP.
                          */
                         tp->pred_flags = 0;
                         tcp_fast_path_check(sk);  // 检验是否能够开启“快速路径”(2)看下面

                         if (nwin > tp->max_window) {   // 如果调整之后的窗口大于从对方接收到的最大的窗口值
                                 tp->max_window = nwin; // 调整为小的
                                 tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie); // 改变mss_cache
                         }
                 }
         }

         tp->snd_una = ack;    // 下一个第一个需要确认就是所有当前已经确认序号之后~~~~

         return flag;
}

/* Check that window update is acceptable.
 * The function assumes that snd_una<=ack<=snd_next.
 */ // 这个函数是检查窗口是否可变,只要确认ack在snd_una~下一个需要发送的数据之间,就是需要改变窗口的!
static inline int tcp_may_update_window(const struct tcp_sock *tp,
                                         const u32 ack, const u32 ack_seq,
                                         const u32 nwin)
{
         return (after(ack, tp->snd_una) ||                       // snd_una<=ack
                 after(ack_seq, tp->snd_wl1) ||                   // 看上面的窗口图可以知道
                 (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd)); // 调整的新窗口大于原始发送窗口
}

看一下检测是否可以进入“快速路径”处理函数:tcp_fast_path_check


static inline void tcp_fast_path_check(struct sock *sk)
{
         struct tcp_sock *tp = tcp_sk(sk);

         if (skb_queue_empty(&tp->out_of_order_queue) &&
            tp->rcv_wnd &&
            atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf &&
             !tp->urg_data)
                 tcp_fast_path_on(tp);
}

能够进入“快速路径”处理的基本条件是:

  • 是否ofo(乱序包队列)队列为空,如果不为空也就是说有乱序数据不可以进入快速路径。
  • 当前的接收窗口是否大于0.,如果不是,不可以进入。
  • 当前的已经提交的数据包大小是否小于接收缓冲区的大小,能够放的下才可以进入快速路径。
  • 是否含有urgent 数据,不含有才可以进入快速路径。

参考

 http://blog.csdn.net/shanshanpt/article/details/21798421 


发表评论

邮箱地址不会被公开。 必填项已用*标注