TCP空闲连接的重启动

在TCP连接空闲一段时间之后,发送端再次开启发送时,可能导致大量的数据发送到网络中。由于一段空闲时间后,TCP发送端不能再使用ACK时钟发送新报文到网络中,所以,可能线速发送一整拥塞窗口的数据,容易造成网络拥塞,而且,网络状况可能已经改变。因此,内核中在空闲时间超过RTO之后,将使用慢启动恢复发送。

空闲检查

在空闲检查函数中,如果内核未打开在空闲之后启用慢启动的功能,即tcp_slow_start_after_idle值为零,或者网络中存在发送的报文;或者拥塞控制算法定义了相关处理,不启用慢启动。否则,如果当前时间与最后一次的发送时间差值大于RTO时长,开启慢启动。

static inline void tcp_slow_start_after_idle_check(struct sock *sk)
{
    const struct tcp_congestion_ops *ca_ops = inet_csk(sk)->icsk_ca_ops;
    struct tcp_sock *tp = tcp_sk(sk);
    s32 delta;

    if (!sock_net(sk)->ipv4.sysctl_tcp_slow_start_after_idle || tp->packets_out ||
        ca_ops->cong_control)
        return;
    delta = tcp_jiffies32 - tp->lsndtime;
    if (delta > inet_csk(sk)->icsk_rto)
        tcp_cwnd_restart(sk, delta);
}

如下tcp_cwnd_restart函数,重启动窗口值首先赋值为tcp_init_cwnd函数的计算值,其一般情况下等于初始窗口值TCP_INIT_CWND(10),但是,如果在路由项中缓存了初始窗口值,等于缓存值。慢启动阈值ssthresh等于其当前值与拥塞窗口*3/4两者之间的最大值。其次,重启动窗口值选择其与当前窗口值两者之间的较小值,如果当前拥塞窗口大于重启动窗口,空闲时长每经过RTO时段,将当前拥塞窗口减半。最后,最终的拥塞窗口等于拥塞窗口与重启动窗口之间的最大值。

/* RFC2861. Reset CWND after idle period longer RTO to "restart window".
 * This is the first part of cwnd validation mechanism.
 */
void tcp_cwnd_restart(struct sock *sk, s32 delta)
{
    struct tcp_sock *tp = tcp_sk(sk);
    u32 restart_cwnd = tcp_init_cwnd(tp, __sk_dst_get(sk));
    u32 cwnd = tp->snd_cwnd;

    tcp_ca_event(sk, CA_EVENT_CWND_RESTART);

    tp->snd_ssthresh = tcp_current_ssthresh(sk);
    restart_cwnd = min(restart_cwnd, cwnd);

    while ((delta -= inet_csk(sk)->icsk_rto) > 0 && cwnd > restart_cwnd)
        cwnd >>= 1;
    tp->snd_cwnd = max(cwnd, restart_cwnd);
    tp->snd_cwnd_stamp = tcp_jiffies32;
    tp->snd_cwnd_used = 0;
}

如下在传输函数__tcp_transmit_skb中,调用tcp_event_data_sent更新最后的发送时间到变量lsndtime中,这里注意只有当发送数据报文时,才更新发送时间。

static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{
    ...
    if (skb->len != tcp_header_size) {
        tcp_event_data_sent(tp, sk);

static void tcp_event_data_sent(struct tcp_sock *tp, struct sock *sk)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    const u32 now = tcp_jiffies32;

    if (tcp_packets_in_flight(tp) == 0)
        tcp_ca_event(sk, CA_EVENT_TX_START);

    tp->lsndtime = now;

空闲检查路径

在TCP报文发送流程中,函数skb_entail将skb添加到套接口发送队列sk_write_queue末尾,函数最后,调用tcp_slow_start_after_idle_check检查函数,即在发送报文前检查空闲时间是否超时。

static void skb_entail(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);

    skb->csum    = 0;
    tcb->seq     = tcb->end_seq = tp->write_seq;
    tcb->tcp_flags = TCPHDR_ACK;
    tcb->sacked  = 0;
    __skb_header_release(skb);
    tcp_add_write_queue_tail(sk, skb);
    ...

    tcp_slow_start_after_idle_check(sk);
}

另外,在接收到对端ACK报文时,如果此报文更新了发送窗口,并且发送队列不为空,调用空闲检查函数。如果发送端是因为发送窗口为空,而停止发送,当发送窗口再次打开时,继续发送之前,需要进行此空闲检查。

static int tcp_ack_update_window(struct sock *sk, const struct sk_buff *skb, u32 ack, u32 ack_seq)
{
    struct tcp_sock *tp = tcp_sk(sk);
    u32 nwin = ntohs(tcp_hdr(skb)->window);

    if (likely(!tcp_hdr(skb)->syn))
        nwin <<= tp->rx_opt.snd_wscale;

    if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {
        flag |= FLAG_WIN_UPDATE;
        tcp_update_wl(tp, ack_seq);

        if (tp->snd_wnd != nwin) {
            tp->snd_wnd = nwin;
            ...

            if (!tcp_write_queue_empty(sk))
                tcp_slow_start_after_idle_check(sk);

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页