乐趣区

关于tcp:TCP-滑动窗口是个什么东西这篇讲清楚

明天咱们来看 TCP 的滑动窗口问题,无论是在工作中,还是在口试面试中,滑动窗口都是十分重要的概念,明天,图文并茂给大家讲清楚,一起来看看。

一、TCP 的劣势

TCP 通过多年厮杀,早已确立了松软的江湖根底,是 面向连贯,牢靠,基于字节流的传输层协定。所谓牢靠,就是确保数据精确的,不反复,无提早的达到目的地;

TCP 的总结如下:

①数据分片:在发送端对用户数据进行分片,在接收端进行重组,由 TCP 确定分片的大小并管制分片和重组;

②达到确认:接收端接管到分片数据时,依据分片数据序号向发送端发送一个确认;

③超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;

④滑动窗口:TCP 连贯每一方的接管缓冲空间大小都固定,接收端只容许另一端发送接收端缓冲区所能接收的数据,TCP 在滑动窗口的根底上提供流量管制,避免较快主机以致较慢主机的缓冲区溢出;

⑤失序解决:作为 IP 数据报来传输的 TCP 分片达到时可能会失序,TCP 将对收到的数据进行从新排序,将收到的数据以正确的程序交给应用层;

⑥反复解决:作为 IP 数据报来传输的 TCP 分片会产生反复,TCP 的接收端必须抛弃反复的数据;

⑦数据校验:TCP 将放弃它首部和数据的测验和,这是一个端到端的测验和,目标是检测数据在传输过程中的任何变动。如果收到分片的测验和有过错,TCP 将抛弃这个分片,并不确认收到此报文段导致对端超时并重发。

其中很重要的一环就是“滑动窗口”,上面咱们重点关注一下。

二、滑动窗口的引入

IP 层协定属于 不牢靠 的协定,IP 层并不关系数据是否发送到了对端,在简单的网络中,因为各种各样的起因,接管到数据包的程序不肯定和发送的程序雷同,这就是 乱序问题。这种状况下,有必要为每个包定义一个序号 seq,每个包用一个校验和确保数据完整性。

而后 发送方不能不论接管方的承受能力,只顾着发 。举个栗子,一个高速公路如果没有收费站,那么车辆就会一拥而入,此时不凑巧,产生了追尾事变,导致公路拥塞,如果不管制公路的进入车辆,那么整个高速公路都会变成“露天停车场”。说到这里你可能就明确了,TCP 须要这样的“收费站”,而这个收费站就是“ 滑动窗口”。

而后,平时在高速上的时候,仔细的你留神到了:除了入口有个收费站,进口也有个收费站。TCP 也是一样的,除了入口有发送方滑动窗口,出口处也设立有接管方滑动窗口。

收费站除了限度流速以外还有什么作用鸭?是不是要免费呢,毕竟这是国家修的路,不能白走是吧。

对于 发送方滑动窗口(入口收费站),咱们把数据包看成车辆,枚举它们的状态:

  1. 还未进入入口收费站车辆。对应的是下图 Not Sent,Recipient Not Ready to Receive。这些数据属于 发送端未发送 ,同时 接收端也未筹备接管的
  2. 进入收费站,但未进入高速路。对应的是图中的 Not Sent,Recipient Ready to Receive。这部分数据是 发送端未发送 但曾经告知接管方的,这部分其实曾经在窗口中(发送端缓存)了,期待发送。
  3. 在高速公路上行驶的车辆。对应的是 Send But Not Yet Acknowledged。这部分数据称为 发送但没有被确认 ,数据被发送进来,没有收到接收端的 ACK,认为并没有实现发送,这个属于 窗口内的数据
  4. 达到进口收费站的车辆。对应的是 Sent and Acknowledged。这些数据表示 曾经发送胜利并曾经被确认的数据,这些数据曾经来到窗口了。

对于 接管方滑动窗口(进口收费站),相似发送端,接收端的数据有 4 个分类,因为接收端并不需要期待 ACK 所以它没有相似的接管并确认了的分类,状况如下

  1. 车辆还未达到进口收费站。对应 Not Received:有空位,还没有被接管的数据
  2. 车辆达到进口收费站,但未实现缴费。对应 Received Not ACK: 曾经接管并,然而还没有回复 ACK,这些包可能输属于 Delay ACK 的领域了。
  3. 车辆实现缴费,但不晓得走哪条路。对应 Received and ACK Not Send to Process:这部分数据属于接管了数据然而还没有被下层的应用程序接管,也是被缓存在窗口内。
  4. 车辆来到进口收费站。对应 Received and ACK Send to Process。来到了窗口缓存。

这样讲是不是就很明确了,上面给出滑动窗口的正式定义。

  1. Left edge 和 Right edge 别离示意滑动窗口的左边界和右边界。
  2. Usable Window:示意窗口的缓冲区。
  3. Send Window:发送窗口,这部分值是有接管方在三次握手的时候进行设置的,同时在接管过程中也一直地通告能够发送的窗口大小,来进行适应。
  4. Window Already Sent: 曾经发送的数据,然而并没有收到 ACK。

滑动窗口所谓的“滑动”,并不是说窗口在动,而是因为数据在一直进入和来到窗口,也就是说真正“动”的是数据,上面一幅图就示意了这点:

滑动窗口在 TCP 首部中的地位如下图所示:

RFC793 对它的解释是 ” 发送方心愿接管到的以 ACK 标记结尾的数据字节数 ”。滑动窗口是跟 ACK 一起的,因而 ACK 标记必须置为 1,同时指定窗口大小。能够看到,滑动窗口大小通过 16 个 bit 来形容,所以变动范畴 0 -65535(这个范畴其实是能够缩放的)。

Window:  16 bits
    The number of data octets beginning with the one indicated in the
    acknowledgment field which the sender of this segment is willing to
    accept.

三、滑动窗口的工作原理

首先,TCP 不是每个报文段都会返回 TCP 的,可能对 多个报文返回一个 ACK

咱们再举一个栗子,制作某机器须要 A,B,C 三种整机,并且组装程序 A→B→C,某天不凑巧,B,C 整机先到,这个时候往往把 A 的地位预留进去,期待 A 达到之后,再进行组装;如果 A 失落了,那么 B,C 也丢失作用,被抛弃了。

在 TCP 中也有这样一个“预留的中央”,咱们称之为“空洞(hole)”。假如咱们顺次发送 3 个报文(A,B,C)。如果 B,C 报文先到,那么先把 A 的地位预留进去,只有 A 报文达到了,接收端才返回 一个 ACK(不是 3 个)进行确认

在介绍滑动窗口原理之前,咱们先解说一个重要概念——MSS (Max Segment Size,最大段大小),数据被 TCP 宰割成适合发送的数据块 ,称为段(Segment)。留神:这里说的段(Segment) 不包含协定首部,只蕴含数据

与 MSS 最为相干的一个参数就是网络设备接口的 MTU(Max Transfer Unit)。

咱们残缺讲一下滑动窗口的原理:

  1. 有一组数据通过 TCP 传输,TCP 先 将其分成若干段,假如有四个段 seg1,seg2,seg3,seg4,顺次发送进来,此时假如接收端接管到了 seg1 seg2 seg4;
  2. 此时接收端的行为是回复一个 ACK 包阐明曾经接管到,并将 seg4 进行缓存(保障程序,产生一个保留 seg3 的 hole);
  3. 发送端收到 ACK 之后,就会将对应的数据包变为已确认状态,这个时候窗口向右挪动;
  4. 假如接收端通告的 Window Size 依然不变,此时窗口右移,产生一些新的空位,这些是接收端容许发送的领域;
  5. 对于失落的 seg3,如果超过肯定工夫,TCP 就会从新传送(重传机制),重传胜利会 seg3 seg4 一块被确认,不胜利,seg4 也将被抛弃。

后面咱们讲到,这个滑动窗口是能够动静调整的,上面讲一下滑动窗口动静调整的原理。

四、滑动窗口的动静调整原理

这一部分可能须要浏览 Linux 源码。有趣味的读者能够来肝哦。

内核版本: linux3.2.12

文件目录:linux-3.2.12\include\linux\tcp.h

struct tcp_sock {
    ...
    /* 最早接管但未确认的段的序号,即以后接管窗口的左端 */
    u32 rcv_wup; /* rcv_nxt on last window update sent */
    u16 advmss; /* Advertised MSS. 本端能接管的 MSS 下限,建设连贯时用来通告对端 */
    u32 rcv_ssthresh; /* Current window clamp. 以后接管窗口大小的阈值 */
    u32 rcv_wnd; /* Current receiver window,以后的接管窗口大小 */
    u32 window_clamp; /* 接管窗口的最大值,这个值也会动静调整 */
    ...
    struct tcp_options_received rx_opt; /* 接管选项 */ 
    u32  mss_cache;  /* Cached effective mss, not including SACKS */
}

struct tcp_options_received {
    ...
        snd_wscale : 4, /* Window scaling received from sender, 对端接管窗口扩充因子 */
        rcv_wscale : 4; /* Window scaling to send to receiver, 本端接管窗口扩充因子 */
    u16 user_mss; /* mss requested by user in ioctl */
    u16 mss_clamp; /* Maximal mss, negotiated at connection setup,对端的最大 mss */
}

struct tcp_options_received {
/*  PAWS/RTTM data  */
  long  ts_recent_stamp;/* Time we stored ts_recent (for aging) */
  u32  ts_recent;  /* Time stamp to echo next    */
  u32  rcv_tsval;  /* Time stamp value               */
  u32  rcv_tsecr;  /* Time stamp echo reply          */
  u16   saw_tstamp : 1,  /* Saw TIMESTAMP on last packet    */
    tstamp_ok : 1,  /* TIMESTAMP seen on SYN packet    */
    dsack : 1,  /* D-SACK is scheduled      */
    wscale_ok : 1,  /* Wscale seen on SYN packet    */
    sack_ok : 4,  /* SACK seen on SYN packet    */
    snd_wscale : 4,  /* Window scaling received from sender  */
    rcv_wscale : 4;  /* Window scaling to send to receiver  */
  u8  cookie_plus:6,  /* bytes in authenticator/cookie option  */
    cookie_out_never:1,
    cookie_in_always:1;
  u8  num_sacks;  /* Number of SACK blocks    */
  u16  user_mss;  /* mss requested by user in ioctl  */
  u16  mss_clamp;  /* Maximal mss, negotiated at connection setup */
};

tcp_sock 示意的是 TCP 构造体。咱们只关注外面最重要的几个成员:

(1)tp->advmss

这里的 adv 是 advertised 告知的意思。本端在建设连贯时应用的 MSS,是本端能接管的 MSS 下限。这是从路由缓存中取得的(dst->metrics[RTAX_ADVMSS – 1]),个别是 1460。

(2)tp->rx_opt.mss_clamp

对端的能接管的 MSS 下限,其值为 tcp_sock->rx_opt.user_mss 和 对端在建设连贯时通告的 MSS 的较小值。

(3)tp->mss_cache

本端以后无效的发送 MSS, 不包含 SACKS。显然不能超过对端接管的下限,即 tp->mss_cache <= tp->mss_clamp。

(4)tcp_sock->rx_opt.user_mss

用户通过 TCP_MAXSEG 选项设置的 MSS 下限,用于决定本端和对端的接管 MSS 下限。

文件目录:linux-3.2.12\include\net\sock.h

struct sock {
    ...
    struct sk_buff_head sk_receive_queue;
    /* 示意接管队列 sk_receive_queue 中所有段的数据总长度 */
#define sk_rmem_alloc sk_backlog.rmem_alloc

    int sk_rcvbuf; /* 接收缓冲区长度的下限 */
    int sk_sndbuf; /* 发送缓冲区长度的下限 */

    struct sk_buff_head sk_write_queue;
    ...
}

次要对接管和发送缓冲区进行定义。

接管缓存 sk->sk_rcvbuf 分为两局部:
(1)network buffer,个别占 3 /4,这部分是协定可能应用的。
(2)application buffer,个别占 1 /4。
咱们在计算连贯可用接管缓存的时候,并不会应用整个的 sk_rcvbuf,避免应用程序读取数据的速度比网络数据包达到的速度慢时,接管缓存被耗尽的状况。

上面是依据 RFC793 和 RFC1122 定义的窗口更新参数。

咱们先从初始状况开始进行剖析。

/* Determine a window scaling and initial window to offer.
 * Based on the assumption that the given amount of space will be offered.
 * Store the results in the tp structure.
 * NOTE: for smooth operation initial space offering should be a multiple of mss
 * if possible. We assume here that mss >= 1\. This MUST be enforced by all calllers.
 */
void tcp_select_initial_window (int __space, __u32 mss, __u32 *rcv_wnd, __u32 *window_clamp,
                                int wscale_ok, __u8 *rcv_wscale, __u32 init_rcv_wnd)
{unsigned int space = (__space < 0 ? 0 : __space); /* 接管缓存不能为负 */

    /* If no clamp set the clamp to the max possible scaled window。* 如果接管窗口下限的初始值为 0,则把它设成最大。*/
    if (*window_clamp == 0)
        (*window_clamp) = (65535 << 14); /* 这是接管窗口的最大下限 */

    /* 接管窗口不能超过它的下限 */
    space = min(*window_clamp, space); 

    /* Quantize space offering to a multiple of mss if possible.
     * 接管窗口大小最好是 mss 的整数倍。*/
    if (space > mss)
        space = (space / mss) * mss; /* 让 space 为 mss 的整数倍 */

    /* NOTE: offering an initial window larger than 32767 will break some
     * buggy TCP stacks. If the admin tells us it is likely we could be speaking
     * with such a buggy stack we will truncate our initial window offering to
     * 32K - 1 unless the remote has sent us a window scaling option, which
     * we interpret as a sign the remote TCP is not misinterpreting the window
     * field as a signed quantity.
     */
    /* 当协定应用有符号的接管窗口时,则接管窗口大小不能超过 32767*/
    if (sysctl_tcp_workaround_signed_windows)
        (*rcv_wnd) = min(space, MAX_TCP_WINDOW);
    esle
        (*rcv_wnd) = space;

    (*rcv_wscale) = 0;
    /* 计算接管窗口扩充因子 rcv_wscale,须要多大能力示意本连贯的最大接管窗口大小?*/
    if (wscale_ok) {
        /* Set window scaling on max possible window
         * See RFC1323 for an explanation of the limit to 14
         * tcp_rmem[2]为接收缓冲区长度下限的最大值,用于调整 sk_rcvbuf。* rmem_max 为零碎接管窗口的最大大小。*/
        space = max_t(u32, sysctl_tcp_rmem[2], sysctl_rmem_max);
        space = min_t(u32, space, *window_clamp); /* 受限于具体连贯 */

        while (space > 65535 && (*rcv_wscale) < 14) {
            space >>= 1;
            (*rcv_wscale)++;
        }
   }

    /* Set initial window to a value enough for senders starting with initial
     * congestion window of TCP_DEFAULT_INIT_RCVWND. Place a limit on the 
     * initial window when mss is larger than 1460.
     *
     * 接管窗口的初始值在这里确定,个别是 10 个数据段大小左右。*/
    if (mss > (1 << *rcv_wscale)) {
        int init_cwnd = TCP_DEFAULT_INIT_RCVWND; /* 10 */
        if (mss > 1460)
            init_cwnd = max_t(u32, 1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);

        /* when initializing use the value from init_rcv_wnd rather than the 
         * default from above.
         * 决定初始接管窗口时,先思考路由缓存中的,如果没有,再思考零碎默认的。*/
        if (init_rcv_wnd) /* 如果路由缓存中初始接管窗口大小不为 0 */
            *rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss);
        else 
            *rcv_wnd = min(*rcv_wnd, init_cwnd *mss);
    }

    /* Set the clamp no higher than max representable value */
    (*window_clamp) = min(65535 << (*rcv_wscale), *window_clamp);
}

初始的接管窗口的取值(mss 的整数倍):

(1)先思考路由缓存中的 RTAX_INITRWND
(2)在思考零碎默认的 TCP_DEFAULT_INIT_RCVWND(10)
(3)最初思考 min(3/4 * sk_rcvbuf, window_clamp),如果这个值很低.

接下来咱们能够看到,接管窗口的大小次要取决于残余的接管缓存,以及接管窗口以后阈值。

决定接管窗口大小的函数 tcp_select_window()在 tcp_transmit_skb()中调用,也就是说每次咱们要发送数据包时,都要应用 tcp_select_window()来决定通告的接管窗口大小。

static int tcp_transmit_skb (struct sock *sk, struct sk_buff *skb, int clone_it, 
                             gfp_t gfp_mask)
{const struct inet_connection_sock *icsk = inet_csk(sk);
    struct inet_sock *inet;
    struct tcp_sock *tp;
    struct tcp_skb_cb *tcb;
    struct tcphdr *th;
    ...
    /* Build TCP header and checksum it,以下是 TCP 头的赋值 */
    th = tcp_hdr(skb); /* skb->transport_header */
    th->source = inet->inet_sport;
    th->dest = inet->inet_dport;
    th->seq = htonl(tcb->seq);
    th->ack_seq = htonl(tp->rcv_nxt);
    /* 这个语句能够看出 C 语言的弱小 */
    *(((__be16 *) th) + 6) = htons(((tcp_header_size >> 2) << 12) | tcb->tcp_flags);

    if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
        /* RFC1323: The window in SYN & SYN/ACK segments in never scaled.
         * 从这里咱们能够看到,在三次握手阶段,接管窗口并没有按扩充因子缩放。*/
        th->window = htons(min(tp->rcv_wnd, 65535U));

    } else {th->window = htons(tcp_select_window(sk)); /* 更新接管窗口的大小 */
    }
    th->check = 0;
    th->urg_ptr = 0;
    ...
}

这里有几个函数,大家可能没见过,所以略微解释一下:

网络字节程序 NBO(Network Byte Order)

按从高到低的顺序存储,在网络上应用同一的网络字节程序,可防止兼容性问题;

主机字节程序 HBO(Host Byte Order)

不同的机器 HBO 不雷同,与 CPU 的设计无关,数据的程序是由 CPU 决定的,而与操作系统无关;

如 Intel x86 构造下,short 型数 0x1234 示意为 34 12,int 型数 0x12345678 示意为 78 56 34 12;

如 IBM power PC 构造下,short 型数 0x1234 示意为 12 34,int 型数 0x12345678 示意为 12 34 56 78.

因为这个起因,不同体系结构的机器之间不能间接通信,所以要转换成一种约定的程序,也就是网络字节程序,其实就是如同 power pc 那样的程序。

ntohs =net to host short int 16 位 htons=host to net short int 16 位 ntohl =net to host long int 32 位 htonl=host to net long int 32 位

接下来看一下 tcp_select_window(),这个是外围函数。

static u16 tcp_select_window(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);

    u32 cur_win = tcp_receive_window(tp); /* 以后接管窗口的残余大小 */
    u32 new_win = __tcp_select_window(sk); /* 依据残余的接管缓存,计算新的接管窗口的大小 */

    /* Never shrink the offered window,不容许放大已调配的接管窗口 */
    if (new_win < cur_win) {
        /* Danger Will Robinson!
         * Don't update rcv_wup/rcv_wnd here or else
         * we will not be able to advertise a zero window in time. --DaveM
         * Relax Will Robinson.
         */
        new_win = ALIGN(cur_win, 1 << tp->rx_opt.rcv_wscale);
    }

    /* 更新接管窗口大小。集体感觉这句代码应该后移,因为此时接管窗口的大小还未最终确定!*/
    tp->rcv_wnd = new_win;
    tp->rcv_wup = tp->rcv_nxt; /* 更新接管窗口的左边界,把未确认的数据累积确认 */

    /* 确保接管窗口大小不超过规定的最大值。* Make sure we do not exceed the maximum possible scaled window.
     */
    if (! tp->rx_opt.rcv_wscale && sysctl_tcp_workaround_signed_windows)
        /* 不能超过 32767,因为一些奇葩协定采纳有符号的接管窗口大小 */
        new_win = min(new_win, MAX_TCP_WINDOW); 

    else
        new_win = min(new_win, (65535U << tp->rx_opt.rcv_wscale));

    /* RFC1323 scaling applied. 按比例因子放大接管窗口,这样最多能示意 30 位 */
    new_win >>= tp->rx_opt.rcv_wscale;

    /* If we advertise zero window, disable fast path. */
    if (new_win == 0)
        tp->pred_flags = 0;

    return new_win; /* 返回最终的接管窗口大小 */
}

每次发送一个 TCP 数据段,都要构建 TCP 首部,这时会调用 tcp_select_window 抉择接管窗口大小。窗口大小抉择的根本算法:

  1. 计算以后接管窗口的残余大小 cur_win。
  2. 计算新的接管窗口大小 new_win,这个值为残余接管缓存的 3 /4,且不能超过 rcv_ssthresh。
  3. 取 cur_win 和 new_win 中值较大者作为接管窗口大小。

计算以后接管窗口的残余大小 cur_win。

/* 
 * Compute the actual receive window we are currently advertising.
 * rcv_nxt can be after the window if our peer push more data than
 * the offered window.
 */
static inline u32 tcp_receive_window (const struct tcp_sock *tp)
{
    s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;

    if (win < 0)
        win = 0;

    return (u32) win;
}

__tcp_select_window 计算新的接管窗口大小 new_win,这个是要害函数,咱们将看到 rcv_ssthresh 所起的作用。

/* 
 * calculate the new window to be advertised.
 */
u32 __tcp_select_window(struct sock *sk)
{struct inet_connection_sock *icsk = inet_csk(sk);
    struct tcp_sock *tp = tcp_sk(sk);

    /* MSS for the peer's data. Previous versions used mss_clamp here.
     * I don't know if the value based on our guesses of peer's MSS is better
     * for the performance. It's more correct but may be worse for the performance
     * because of rcv_mss fluctuations. —— SAW 1998/11/1
     */
    int mss = icsk->icsk_ack.rcv_mss;/* 这个是预计目前对端无效的发送 mss,而不是最大的 */  
    int free_space = tcp_space(sk); /* 残余接管缓存的 3 /4 */
    int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk)); /* 总的接管缓存 */
    int window;

    if (mss > full_space)
        mss = full_space; /* 减小 mss,因为接管缓存太小了 */

    /* receive buffer is half full,接管缓存应用一半以上时要小心了 */
    if (free_space < (full_space >> 1)) {
        icsk->icsk_ack.quick = 0; /* 能够疾速发送 ACK 段的数量置零 */

        if (tcp_memory_pressure)/* 有内存压力时,把接管窗口限度在 5840 字节以下 */
            tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U * tp->advmss);

        if (free_space < mss) /* 残余接管缓存不足以接管 mss 的数据 */
            return 0;
    }

    if (free_space > tp->rcv_ssthresh)
        /* 看!不能超过以后接管窗口阈值,这能够达接管窗口平滑增长的成果 */
        free_space = tp->rcv_ssthresh;  

    /* Don't do rounding if we are using window scaling, since the scaled window will
     * not line up with the MSS boundary anyway.
     */
    window = tp->rcv_wnd;
    if (tp->rx_opt.rcv_wscale) { /* 接管窗口扩充因子不为零 */
        window = free_space;

        /* Advertise enough space so that it won't get scaled away.
         * Import case: prevent zero window announcement if 1 << rcv_wscale > mss.
         * 避免四舍五入造通告的接管窗口偏小。*/
        if (((window >> tp->rx_opt.rcv_wscale) << tp->rx_opt.rcv_wscale) != window)
            window =(((window >> tp->rx_opt.rcv_wscale) + 1) << tp->rx_opt.rcv_wscale);

    } else {
        /* Get the largest window that is a nice multiple of mss.
         * Window clamp already applied above.
         * If our current window offering is within 1 mss of the free space we just keep it.
         * This prevents the divide and multiply from happening most of the time.
         * We also don't do any window rounding when the free space is too small.
         */
        /* 截取 free_space 中整数个 mss,如果 rcv_wnd 和 free_space 的差距在一个 mss 以上 */
        if (window <= free_space - mss || window > free_space) 
            window = (free_space / mss) * mss;
        /* 如果 free space 过小,则间接取 free space 值 */
        else if (mss = full_space && free_space > window + (full_space >> 1))
            window = free_space;
        /* 当 free_space -mss < window < free_space 时,间接应用 rcv_wnd,不做批改 */
    }    

    return window;
}
/* 残余接管缓存的 3 /4。* Note: caller must be prepared to deal with negative returns.
 */
static inline int tcp_space (const struct sock *sk)
{return tcp_win_from_space(sk->sk_rcvbuf - atomic_read(&sk->sk_rmem_alloc));
}

static inline int tcp_win_from_space(int space)
{return sysctl_tcp_adv_win_scale <= 0 ? (space >> (-sysctl_tcp_adv_win_scale)) :
        space - (space >> sysctl_tcp_adv_win_scale);
}

/* 最大的接管缓存的 3 /4 */
static inline int tcp_full_space(const struct sock *sk)
{return tcp_win_from_space(sk->sk_rcvbuf);
}

总体来说,新的接管窗口大小值为:残余接管缓存的 3 /4,但不能超过接管缓存的阈值。

总之,接管窗口的调整算法次要波及:

(1)window_clamp 和 sk_rcvbuf 的调整。

(2)rcv_ssthresh 接管窗口以后阈值的动静调整,个别增长 2 *advmss。

(3)rcv_wnd 接管窗口的动静调整,个别为 min(3/4 free space in sk_rcvbuf, rcv_ssthresh)。

如果残余的接管缓存够大,rcv_wnd 受限于 rcv_ssthresh。这个时候每收到一个大的数据包,rcv_wnd 就增大 2920 字节(因为缩放起因这个值可能稳定)。这就像慢启动一样,接管窗口指数增长。

接管窗口当然不能无限度增长,当它增长到肯定大小时,就会受到一系列因素的限度,比方 window_clamp 和 sk_rcvbuf,或者残余接管缓存区大小。

当应用程序读取接收缓冲区数据不够快时,或者产生了丢包时,接管窗口会变小,这次要受限于残余的接管缓存的大小。

五、试验环节

这里要给大家举荐一个超好用的网络协议剖析工具,wireshark。网上能够收费下载。

我抉择的是无线网卡 WLAN

这里应用 wireshark 抓取 baidu.com 的 TCP 报文。能够看到,No.36 是本机对服务器之前发送数据(No.31)的一个 ACK 确认(ACK 的 Flag 标记成 1),同时申明窗口大小(window size)为 1040。

紧接着是 No.37 对 No.30 发送一个 ACK 确认,受零碎过程资源的影响,这时窗口的大小动静调整为 948。

能够看到滑动窗口的确是会主动调整的。

六、总结

在这篇中,咱们认真全面地探讨了 TCP 滑动窗口的原理,从根本定义到源码剖析包罗万象。一般而言,筹备面试的话不须要到源码那一步的。

TCP 滑动窗口次要有以下作用:

1. TCP 在滑动窗口的根底上提供流量管制,避免较快主机以致较慢主机的缓冲区溢出,次要是依据网络状况 动静调整窗口大小

2. TCP 将数据分段发送,确保发送的数据达到接收端也是正确的。利用的是“空洞”。TCP 不会对每个数据包都返回 ACK,而是累计返回的。

</article>

退出移动版