TCP拥塞控制慢启动窗口设置

TCP拥塞控制慢启动窗⼝设置
拥塞控制及慢启动
通塞控制:Congestion Control
简单的说,就是TCP传输过程中,为了避免⼀下⼦将⽹络冲爆,引⼊的机制。⽽慢启动,顾名思义,⼀开始慢慢传,发现没有问题,再增加传输速度。⽽⼀旦发现传输有超时,协议会认为⽹络拥堵,⼜降低传输速度。
起始的传输速度,就是由初始拥塞窗⼝,initial congestion window,简称initcwnd参数控制的。
alikernel 2.6.32的内核,initcwnd的初始化,在net/ipv4/tcp_input.c:
__u32 tcp_init_cwnd(struct tcp_sock *tp, struct dst_entry *dst)
{
__u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);  //可以被调整
if (!cwnd)
cwnd = TCP_INIT_CWND; //默认的值
return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
}
在include/net/tcp.h定义了TCP_INIT_CWND这个值为10:
/* TCP initial congestion window */
#define TCP_INIT_CWND          10
这⾥的10代表⼀开始可以传输10*MSS的数据。
我们的MSS=1460,因此初始可以传输14600字节,⼤约15KB的数据。
设置⽅式
ip route命令⽀持调整指定路由的initcwnd:
#ip route change 10.0.0.0/8 via 10.83.251.247 dev bond0 initcwnd 30
#ip route list 10.0.0.0/8
10.0.0.0/8 via 10.83.251.247 dev bond0  initcwnd 30
它是通过netlink的接⼝,调整了上述dst_entry⾥边的RTAX_INITCWND,覆盖了默认的值。
场景
客户端访问server端,每次需要返回的数据⼤约20-30KB,⽽默认的initcwnd,初始只能⼀次性传输15KB左右,这就涉及到要分两次传输,多⼀个round trip的时间
⽤户希望能够提升访问速度,让server端能够⼀次将所有数据传输过来,⽽不是分两次。因此,就涉及到调整初始拥塞窗⼝的调整。
问题
发现,调整了server/clinet端的initcwnd后,传输速度并没有改善。
curl测试
通过curl测试的⽅式,可以看到:
curl -w %{time_starttransfer}:%{time_total}:%{size_download}"\n"'url/'
0.287:0.417:30601
time_starttransfer:从开始到第⼀个字节开始传输的时间
time_total:整个操作持续的时间
size_download:传输的字节数
这⾥看到total时间⽐开始传输的时间⼤不少,应该是分两次传输的。
⽽对于⼩包,结果应该是这样的:这⾥传输4574B<10*MSS,在慢启动初始阶段就传完了,total时间和开始传输的时间基本是⼀致的。
curl -w %{time_starttransfer}:%{time_total}:%{size_download}"\n"'url/'
0.288::0.288::4574
抓包分析
并且,通过抓包也能够证明,initcwnd依旧是10,如图:
10.137.18.42为client端
10.98.60.74为server端
server端建⽴好连接并收到HTTP GET请求之后,开始发送数据:
在17:49:17.22xxxxx这个时间点,连续发送了1514+4410+2491+5858=10*MSS
然后在下个时间点17:49:17.35xxxxx,单独发送了1514+9531=8*MSS
问题分析
还是看这个抓包⽂件,发现,clinet和server建⽴好连接以后,它的Window为14720=10*MSS,因此发送端还是只能发送10*MSS的包过来,因此,这个设置在这⾥没有⽣效。
2.6.32内核window初始化
2.6.32版本的alikernel中,将这个receive window设置成TCP_DEFAULT_INIT_RCVWND,这个值定义为10,不可更改,因此改⼤了initcwnd,还是不⽣效。
net/ipv4/tcp_output.c
void tcp_select_initial_window(int __space, __u32 mss,
__u32 *rcv_wnd, __u32 *window_clamp,
int wscale_ok, __u8 *rcv_wscale)
{
unsigned int space = (__space < 0 ? 0 : __space);
........
/* 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.
*/
if (mss > (1 << *rcv_wscale)) {
int init_cwnd = TCP_DEFAULT_INIT_RCVWND;
if (mss > 1460)
init_cwnd =
max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);
*rcv_wnd = min(*rcv_wnd, init_cwnd * mss);
}
........
}
3.10内核window初始化
在3.10版本的alikernel中,receive window的初始化⽅式已经做了改变:
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 (mss > (1 << *rcv_wscale)) {
if (!init_rcv_wnd) /* Use default unless specified otherwise */
init_rcv_wnd = tcp_default_init_rwnd(mss);
*rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss);
}
.
.......
}
这个函数传⼊了⼀个参数,init_rcv_wnd,如果没有设置,则通过tcp_default_init_rwnd函数拿到默认值,为两倍的
dst指数
TCP_INIT_CWND*2=20。可以看到默认的window做了优化,有10*MSS改成了20*MSS
u32 tcp_default_init_rwnd(u32 mss)
{
/* Initial receive window should be twice of TCP_INIT_CWND to
* enable proper sending of new unsent data during fast recovery
* (RFC 3517, Section 4, NextSeg() rule (2)). Further place a
* limit when mss is larger than 1460.
*/
u32 init_rwnd = TCP_INIT_CWND * 2;
if (mss > 1460)
init_rwnd = max((1460 * init_rwnd) / mss, 2U);
return init_rwnd;
}
init_rcv_wnd的来源从调⽤端可以看到,是从dst的RTAX_INITRWND拿到的:
tcp_select_initial_window(tcp_full_space(sk),
tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0),
&tp->rcv_wnd,
&tp->window_clamp,
sysctl_tcp_window_scaling,
&rcv_wscale,
dst_metric(dst, RTAX_INITRWND));
之前我们知道,这是通过ip route设置的,因此可以猜测是新版的内核中,⽀持通过ip route修改initrwnd。查阅ip route的man page,确实多了⼀个initrwnd参数的设置:
3.10内核测试
server端initcwnd设置为30
client端内核为3.10,分两步测试:
1、不做任何修改
2、将initrwnd修改为30,ip route change 10.0.0.0/8 via 10.83.251.247 dev bond0 initrwnd 30
client端默认
抓包分析
截图如下:建⽴连接时,client的window为29200=20*MSS=20*1460
分两次,将⼤于20*MSS的数据发出去。
curl的结果:
time_first:286 ms
time_total:414 ms
total_byte:30333 bytes
client端initrnwd改为30
抓包分析
截图如下:建⽴连接时,client的window已经变成43200=30*MSS=30*1460,并且在21:31:36.97xxxx时间点,⼀下⼦将所有的数据包都发出去了,总数⼤于20*MSS
curl的结果:
time_first:285 ms
time_total:285 ms
total_byte:30452 bytes
从结果中,可以看到,3.10版本内核的client,可以通过设置initrwnd,真正实现提升慢启动的速度。并且,对于短连接应⽤,响应速度提升明显。(⼩于15KB的包,没有变化)
initcwnd对长连接的影响
我们知道,拥塞窗⼝会涨,并且涨的速度还挺快:
每当收到⼀个ACK,cwnd++; 线性上升
每当过了⼀个RTT,cwnd = cwnd*2; 指数上升
当然,有最⼤限制。
理论上,对于长连接,跑了⼀段时间后,cwnd肯定会涨到很⾼。但是当连接空闲⼀段时间后,⼜会重新回到慢启动过程,如
下,tcp_cwnd_restart函数,重置cwnd。
static void tcp_event_data_sent(struct tcp_sock *tp,
struct sk_buff *skb, struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
const u32 now = tcp_time_stamp;
if (sysctl_tcp_slow_start_after_idle&&
(!tp->packets_out && (s32)(now - tp->lsndtime) > icsk->icsk_rto))
tcp_cwnd_restart(sk, __sk_dst_get(sk));
......
}
当tcp_slow_start_after_idle这个内核参数开启了(默认开启),并且⼀段时间没有包传输,则会重新进⼊慢启动,这段时间为 icsk-
>icsk_rto=inet_csk(sk)->icsk_rto,等于TCP_TIMEOUT_INIT,这个常量在include/net/tcp.h中定义为3s(2.6.32内核为3s,3.10内核为1s)
#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value  */
因次,要让长连接不会重新进⼊慢启动,可以关闭tcp_slow_start_after_idle:
#sysctl -w p_slow_start_after_idle=0

本文发布于:2024-09-21 16:36:31,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/380313.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:传输   启动   时间
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议