TCP 滑动窗口是个什么东西?这篇讲清楚

article/2025/9/30 19:13:13

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

一、TCP的优势

TCP经过多年厮杀,早已确立了坚实的江湖基础,是**面向连接,可靠,基于字节流的传输层协议。**所谓可靠,就是确保数据准确的,不重复,无延迟的到达目的地;

TCP的总结如下:

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

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

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

④滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;

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

⑥重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;

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

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

二、滑动窗口的引入

IP 层协议属于不可靠的协议,IP 层并不关系数据是否发送到了对端,在复杂的网络中,由于各种各样的原因,接收到数据包的顺序不一定和发送的顺序相同,这就是乱序问题。这种情况下,有必要为每个包定义一个序号seq,每个包用一个校验和确保数据完整性。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ithIVwWy-1665280372036)(https://upload-images.jianshu.io/upload_images/27937678-b5cb94c6af0b85c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

然后,平时在高速上的时候,细心的你注意到了:除了入口有个收费站,出口也有个收费站。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。这些数据表示已经发送成功并已经被确认的数据,这些数据已经离开窗口了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNI1dtvn-1665280372040)(https://upload-images.jianshu.io/upload_images/27937678-f770891aa86781d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

对于接收方滑动窗口(出口收费站),类似发送端,接收端的数据有 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。离开了窗口缓存。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5U9qJwt-1665280372061)(https://upload-images.jianshu.io/upload_images/27937678-4a1837486f384e52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

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

滑动窗口所谓的“滑动”,并不是说窗口在动,而是因为数据在不断进入和离开窗口,也就是说真正“动”的是数据,下面一幅图就表示了这点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6y24154V-1665280372062)(https://upload-images.jianshu.io/upload_images/27937678-8d6ea9a0ef3319ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vgb3KxyM-1665280372063)(https://upload-images.jianshu.io/upload_images/27937678-fc12770c6fc9d11b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

RFC793对它的解释是"发送方希望接收到的以ACK标志开头的数据字节数"。滑动窗口是跟ACK一起的,因此ACK标志必须置为1,同时指定窗口大小。可以看到,滑动窗口大小通过16个bit来描述,所以变化范围0-65535(这个范围其实是可以缩放的)。

Window:  16 bitsThe number of data octets beginning with the one indicated in theacknowledgment field which the sender of this segment is willing toaccept.

三、滑动窗口的工作原理

首先,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_allocint 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定义的窗口更新参数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DmvxwMDk-1665280372064)(https://upload-images.jianshu.io/upload_images/27937678-877aa326caeb1ff7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

我们先从初始情况开始进行分析。

/* 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); elsenew_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,而是累计返回的。


http://chatgpt.dhexx.cn/article/OBeIkkGJ.shtml

相关文章

计算机网络之TCP滑动窗口

文章目录 计算机网络之TCP滑动窗口1.详细讲一下TCP的滑动窗口2. 聊聊 TCP 的滑动窗口 计算机网络之TCP滑动窗口 1.详细讲一下TCP的滑动窗口 在进行数据传输时&#xff0c;如果传输的数据比较大&#xff0c;就需要拆分为多个数据包进行发送。TCP 协议需要对数据进行确认后&…

运输层:TCP滑动窗口

一般来说&#xff0c;我们总是希望数据传输得更快一些。但是如果发送方发送的更快&#xff0c;接收方就可能来不及接收&#xff0c;这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快&#xff0c;要让接收方来得及接收。 滑动窗口就是用于实现流量控制的。可以…

TCP滑动窗口机制

滑动窗口机制 **作用&#xff1a;**利用滑动窗口机制可以很方便地在TCP连接上实现对发送方的流量控制。 1 . A给B发送数据&#xff0c;则A有发送缓冲区&#xff0c;B有接收缓冲区。应用层的所有需要发送的数据都被放在了发送者的发送缓冲区。发送窗口是发送缓存中的一部分&am…

TCP 滑动窗口

TCP滑动窗口主要有两个作用&#xff0c;可靠性、流控 TCP 如果都以一个包为单位&#xff0c;每发一个包进行一次确认应答的处理&#xff0c;这样的传输方式包的往返的时间越长和包的数量越多其通信性能就越低。 为了解决这个问题&#xff0c;TCP引入了窗口这个概念。在往返时…

tcp滑动窗口原理

参考&#xff1a;​​​计算机网络-传输层_wiscourper的博客-CSDN博客 TCP 滑动窗口 作用&#xff1a; 1. 提供TCP可靠性&#xff1a;对发送的数据进行确认2. 流量控制&#xff1a;窗口大小随链路变化 一、TCP窗口机制 TCP中窗口大小是指tcp协议一次传输多少个数据。因为TCP是…

TCP的滑动窗口

1. 引入滑动窗口的原因 如果没有滑动窗口&#xff0c;TCP每发送一个数据&#xff0c;都需要等待这一次确认应答。只有收到了上一个数据包的应答&#xff0c;才能再发送下一个。这样效率太低了当引入了滑动窗口机制后&#xff0c;就可以采取累计确认机制。TCP引入了窗口这个概念…

TCP滑动窗口原理终于清楚了!

我们在学习计算机网络的时候&#xff0c;遇到很多知识点。即便是背的滚瓜烂熟&#xff0c;让你去辨别知识点背后的深层逻辑的时候&#xff0c;可能就手足无措了。 比如小邱去面A公司的时候就被问到&#xff1a; 事实上&#xff0c;这个问题很大程度弥补我计算机网络的“漏洞”…

TCP滑动窗口模拟实战

1.TCP滑动窗口机制 客户端与服务端之间的通信是一个数据传输的过程&#xff0c;消息以数据包形式进行传输。 在传输的过程中&#xff0c;通过滑动窗口机制来同时传输多个数据包&#xff1b;发送端根据接收端的处理能力&#xff0c;适当控制发送窗口大小&#xff0c;实现流量控…

TCP滑动窗口机制(附图例)

文章目录 前言一、滑动窗口的引出二、流量控制2.1 16位窗口大小2.2 发送缓冲区2.3 逐步解析滑动窗口运作 三、快重传机制四、拥塞控制&#xff08;仅供参考&#xff09;五、延迟应答与捎带应答&#xff08;略&#xff09;总结 前言 博主个人社区&#xff1a;开发与算法学习社区…

TCP 滑动窗口(快速重传)

目录 一、滑动窗口的来由 二、滑动窗口 1、滑动窗口模型 2、滑动窗口可能出现的状况 (1) 客户端发送的报文丢了&#xff08;快重传机制&#xff09; (2) 服务端发送的ACK丢了&#xff08;先收到了排序靠后的ACK&#xff09; 三、滑动窗口大小为0时&#xff0c;何时才能继…

TCP滑动窗口

1、滑动窗口的概念 TCP每发送一个数据&#xff0c;都需要进行一次应答。当收到了上一个应答&#xff0c;在发下一个数据&#xff0c;但这种方式效率比较低。数据包往返时间越长&#xff0c;通信的效率就越低。   为了解决这个问题&#xff0c;TCP引入了窗口概念。即在接收窗口…

TCP 滑动窗口详解(非常实用)

一、滑动窗口简介 滑动窗口&#xff08;Sliding window&#xff09;是一种流量控制技术。早期的网络通信中&#xff0c;通信双方不会考虑网络的 拥挤情况直接发送数据。由于大家不知道网络拥塞状况&#xff0c;同时发送数据&#xff0c;导致中间节点阻塞掉包&#xff0c; 谁也发…

TCP滑动窗口机制(重要)

本文参考了一些优秀的书籍->图解TCP/IP,TCP协议卷一,小林coding,还有等等的知乎,百度. 小林coding 小林coding 知乎牛客的文章 : 万字长文 | 23 个问题 TCP 疑难杂症全解析_技术交流_牛客网 这篇文章是关于TCP的又一个重要机制-->滑动窗口,下面是这篇文章的思维导图: 目…

TCP滑动窗口详解

相较于UDP&#xff0c;TCP有以下区别&#xff1a; 1、可靠传输 2、流量控制 这两个功能都是依靠滑动窗口来实现的&#xff0c;本文就来解密TCP中的滑动窗口。 TCP实现可靠传输依靠的有 序列号、自动重传、滑动窗口、确认应答等机制。 序列号 首先我们说下序列号&#xff0c…

计算机视觉——SIFT图像匹配算法

第二章&#xff1a;SIFT&#xff08;尺度不变特征变换&#xff09; 1. SIFT简介1.1 SIFT算法特点1.2 SIFT特征检测的步骤 2. 尺度空间3. 高斯模糊3.1 高斯模糊3.2 高斯金字塔 4. DoG金字塔4.1 DoG局部极值检测4.2 DoG去除边缘响应4.2.1低对比度的响应点4.2.2不稳定的边缘响应点…

SIFT图像匹配原理及python实现(源码实现及基于opencv实现)

写在前面 黄宁然&#xff0c;看过你看过的算法&#xff0c;数学不好是硬伤。 问题来源&#xff1a; An***** xue100: https://bbs.csdn.net/topics/*********?spm1001.2014.3001.**77 &#xff08;1&#xff09;相机置于地面&#xff0c;离天花板的高度始终不变。在某位置拍…

计算机视觉——sift特征匹配+opencv(包含sift的低于3.4.3的opencv安装方法)

文章目录 实验环境环境配置&#xff08;低于3.4.3的opencv安装方法&#xff09;虚拟环境的搭建&#xff08;非必要&#xff09;局部图像描述子Harris 角点检测基本原理SIFT特征提取代码实现运行实例匹配图像展示匹配结果和分析 实验环境 系统&#xff1a;Ubuntu16.04 语言&…

使用 Javascript 和 OpenCV.js 进行人脸检测

大家好&#xff0c;我们将研究如何使用常规 Javascript、HTML Canvas 和 OpenCV.js 库从图像中检测人脸。 让我们先看一些例子&#xff0c;然后我们将继续编写代码并解释 如果您需要查看完整的代码库刚刚获得我的 github 存储库 GitHub - reactcodes/face-detection-javascript…

图像处理:局部描述子SIFT算法

目录 0、关于SIFT的介绍 1、兴趣点 2、描述子 SIFT算法实现 1.实例化sift 2.利用sift.detectAndCompute()检测关键点并计算 3.将关键点检测结果绘制在图像上 Opencv实现 总结 0、关于SIFT的介绍 SIFT&#xff0c;即尺度不变特征变换&#xff0c;一种局部特征描述子&…

SIFT3D点云关键点提取详细介绍

1.引言 SIFT3D的理论基础完全是从图像特征SIFT2D中迁移类比过来的&#xff0c;类似的还有Harris3D和Harris6D的理论也是来源于Harris2D的&#xff0c;这些点云特征在PCL库中都有具体的实现。Harris3D和Harris6D目前已经有很好的博客和视频讲解了但是SIFT3D却没有一个比较好的介…