明天咱们来看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>