TCP滑动窗口的功能是实现流量控制。数据接收方只接收seq落入窗口范围内的数据;发送方也不会发送窗口之外的数据,一旦发现窗口太小则会停止发送直到窗口变大,这样TCP数据接收方就能通过窗口通告来控制数据发送方发送数据的速度。窗口的值存储在TCP报文段的window字段中,大小为16bit,即窗口的最大值是65535。如果使用窗口扩大选项(后续讨论),则通告窗口的值为window左移窗口扩大因子个位数。
关于滑动窗口其实只有三个问题需要明晰:
(1)窗口(包括初始窗口)如何生成(即生产)
(2)窗口信息如何更新(即维护)
(3)窗口如何被使用(即消费)
下面一一解答。
6.2.1 窗口生产
初始窗口的设置在发送SYN和SYN|ACK时进行:
2752 void tcp_connect_init(struct sock *sk)
2753 {
2754 const struct dst_entry *dst = __sk_dst_get(sk);
2755 struct tcp_sock *tp = tcp_sk(sk);
2756 __u8 rcv_wscale;
...
2789 tcp_select_initial_window(tcp_full_space(sk),
2790 tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphd r) : 0), //MSS - 时间戳选项大小
2791 &tp->rcv_wnd,
2792 &tp->window_clamp,
2793 sysctl_tcp_window_scaling,
2794 &rcv_wscale,
2795 dst_metric(dst, RTAX_INITRWND));
2796
2797 tp->rx_opt.rcv_wscale = rcv_wscale;
2798 tp->rcv_ssthresh = tp->rcv_wnd; ...
构建SYN|ACK时:
2654 struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,
2655 struct request_sock *req,
2656 struct tcp_fastopen_cookie *foc)
2657 {
...
2692 /* tcp_full_space because it is guaranteed to be the first packet */
2693 tcp_select_initial_window(tcp_full_space(sk),
2694 mss - (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0), //MSS - 时间戳选项大小
2695 &req->rcv_wnd,
2696 &req->window_clamp,
2697 ireq->wscale_ok,
2698 &rcv_wscale,
2699 dst_metric(dst, RTAX_INITRWND));
2700 ireq->rcv_wscale = rcv_wscale;
...
这样看来
计算初始窗口大小的功能是由tcp_select_initial_window函数完成的:
191 void tcp_select_initial_window(int __space, __u32 mss,
192 __u32 *rcv_wnd, __u32 *window_clamp,
193 int wscale_ok, __u8 *rcv_wscale,
194 __u32 init_rcv_wnd)
195 {
196 unsigned int space = (__space < 0 ? 0 : __space); //__space是TCP根据接收缓存的大小计算出来的
197
198 /* If no clamp set the clamp to the max possible scaled window */
199 if (*window_clamp == 0) //*window_clamp的值是通告给对端的最大的窗口值
200 (*window_clamp) = (65535 << 14); //窗口扩大因子最大为14
201 space = min(*window_clamp, space);
202
203 /* Quantize space offering to a multiple of mss if possible. */
204 if (space > mss)
205 space = (space / mss) * mss; //将space整理为mss的整数倍
...
215 if (sysctl_tcp_workaround_signed_windows) //使用有符号的接收窗口
216 (*rcv_wnd) = min(space, MAX_TCP_WINDOW); //设置通告窗口小于32767,否则可能会导致一些不稳定的TCP协议实现的崩溃
217 else
218 (*rcv_wnd) = space; //将通告窗口的值设置为space
219
220 (*rcv_wscale) = 0;
221 if (wscale_ok) {//开启窗口扩大选项
222 /* Set window scaling on max possible window
223 * See RFC1323 for an explanation of the limit to 14
224 */
225 space = max_t(u32, sysctl_tcp_rmem[2], sysctl_rmem_max); //接收缓存最大空间
226 space = min_t(u32, space, *window_clamp);
227 while (space > 65535 && (*rcv_wscale) < 14) { //计算窗口扩大因子
228 space >>= 1;
229 (*rcv_wscale)++;
230 }
231 }
232
233 /* Set initial window to a value enough for senders starting with
234 * initial congestion window of TCP_DEFAULT_INIT_RCVWND. Place
235 * a limit on the initial window when mss is larger than 1460.
236 */
237 if (mss > (1 << *rcv_wscale)) {
238 int init_cwnd = TCP_DEFAULT_INIT_RCVWND;
239 if (mss > 1460)
240 init_cwnd =
241 max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);
242 /* when initializing use the value from init_rcv_wnd
243 * rather than the default from above
244 */
245 if (init_rcv_wnd)
246 *rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss);
247 else
248 *rcv_wnd = min(*rcv_wnd, init_cwnd * mss);
249 }
250
251 /* Set the clamp no higher than max representable value */
252 (*window_clamp) = min(65535U << (*rcv_wscale), *window_clamp);
253 }
tcp_full_space函数返回可用空间的大小:
1056 static inline int tcp_win_from_space(int space)
1057 {
1058 return sysctl_tcp_adv_win_scale<=0 ?
1059 (space>>(-sysctl_tcp_adv_win_scale)) :
1060 space - (space>>sysctl_tcp_adv_win_scale);
1061 }
...
1070 static inline in