TCP滑动窗口与缓冲区

当我们用TCP创建一个连接的时候,细心的同学可能会发现使用free命令会看到cache的大小就会变大,相应的剩余内存会变小。这是因为内核为每个连接建立了缓冲区,这个缓冲区既要用于发送和接收数据,还要用于充当内核与应用程序的桥梁作用。如果缓冲区设置的过小,那么将不能充分利用网络带宽,网络传输速度过慢。如果设置的过大,那么内存资源将很快用完,新的连接将不能建立。故我们需要知道TCP缓冲区的用途,才能正确的配置内存的大小,实现更高的并发。

滑动窗口

TCP为了实现可靠性,每个报文都需要收到对应的ACK报文,如果在RTO(retransmission timeout)的时间间隔内都收不到对方ACK报文,那么发送方就会重发报文。故发送方在收到确认ACK报文前都需要保存发送内存在缓冲区,因为后面重发的时候需要用到。如果我们每次都在收到确认报文后,再发送下一个报文,其流程图如下:
在这里插入图片描述

我们知道在以太网中,最大可以传送的报文大小是1500字节,除掉TCP/IP的头部字节,每个报文的最大实际有效字节就1k字节左右(在TCP中叫MSS, Max Segment Size )。假设网络的RTT时间是10ms(可以通过ping得到),那么在这种方式下每秒只能发送1k*1000/10=100KB,显然传送速度非常的慢。
那么我们能不能批量发送,批量确认呢?TCP的滑动窗口机制就是用于解决这个问题的。接收方把它的处理能力告诉发送方,使其限制发送速度即可,这就是滑动窗口的由来。接收方根据它的缓冲区,可以计算出后续能够接收多少字节的报文,这个数字叫做接收窗口。当内核接收到报文时,必须用缓冲区存放它们,这样剩余缓冲区空间变小,接收窗口也就变小了;当进程调用 read 函数后,数据被读入了用户空间,内核缓冲区就被清空,这意味着主机可以接收更多的报文,接收窗口就会变大。因此,接收窗口并不是恒定不变的,那么怎么把时刻变化的窗口通知给发送方呢?TCP 报文头部中的窗口字段,就可以起到通知的作用。当发送方从报文中得到接收方的窗口大小时,就明白了最多能发送多少字节的报文,这个数字被称为发送方的发送窗口。如果不考虑拥塞控制,发送方的发送窗口就是接收方的接收窗口(由于报文有传输时延,t1 时刻的接收窗口在 t2 时刻才能到达发送端,因此这两个窗口并不完全等价)。
在TCP的报文头部中,有一个2字节的窗口字段,用于通知发送方能够一次接收方能够一次接收的数据大小。当接收端受到窗口大小的时候,就可以根据窗口大小调整一次批量发送的数据的数量,如下图所示:
在这里插入图片描述
但是表示窗口大小只有2个字节,最大只能表示65535个字节,为啥不是65536呢,因为TCP允许设置窗口大小为0,从而关闭窗口,对方会因为窗口大小为0而不在发送数据,这也是我在《TCP四次挥手调优》的博文中说的,导致FIN报文发送不出的原因所在。如果窗口设置成最大,那么就是65535,为了描述方便,就以64k来计算。那么在同样的RTT为10ms的网络中,最大也只有6.4MB的速度。这与我们动辄gb的网络,差距还是很大。那么我们能不能设置更大的窗口呢?
Linux(https://tools.ietf.org/html/rfc1323)提供了参数

cat /proc/sys/net/ipv4/tcp_window_scaling
1

当设置为1时,及开启了动态调整窗口大小的功能,最大可以达到1GB。
既然打开tcp_window_scaling动态调整滑动窗口的大小,那么能不能把窗口调整到最大1GB,那么在一个RTT的时间内,就可以发送1GB的数据,在RTT为10ms的网络中,带宽就可以达到100GB了,显然那是不可能的。

缓冲区动态调整

那有没有什么依据共linux调整窗口的大小呢?上文中提到窗口的大小与缓冲区正相关,那我们是不是可以通过设置缓冲区的上下限供linux动态调整缓冲区的大小呢?我们又可以基于什么原则设置缓冲区的大小呢?

发送缓冲区设置原则

TCP每发送一个报文,都要等收到ACK确认报文才能从发送缓冲区中删除,那些发送出去但还没有收到ACK确认的报文,我们称之为飞行报文。带宽*RTT得到的大小我们称为带宽时延积。由于发送缓冲区的大小决定了发送窗口的大小,而发送窗口的大小又决定了一次能够发送的数据的大小,而一次能够发送的数据的大小又决定了飞行报文的大小,而飞行报文的大小如果与带宽时延积相关,那么就可以最大化的利用网络带宽。故如果发送缓冲区的大小与带宽时延积相等,那么就可以最大化的利用网络带宽。
那么我们能不能通过linux的socket参数SO_SNDBUF参数设置缓冲区的大小呢?如果我们那么设置了,linux就不能动态的调整缓冲区的大小了,那么单机能够同时创建的连接数量就会非常有限,从而影响并发性,而且网络也会一直波动,缓冲区不能一直处于最佳的状态。其实linux提供了参数:

cat /proc/sys/net/ipv4/tcp_wmem
4096    87380   4194304

用于动态设置缓冲区的大小,例子中4096表示缓冲区的最小大小,4194304表示缓冲区的最大大小,87380 表示默认值。发送缓冲区的动态调整比较简单,当发送缓冲区的剩余空间充足的时候,可以减小发送缓冲区的大小,反之则增加缓冲区的大小。最小值一般就用4096即可,最大值我们可以设置成带宽时延积的大小。在高并发的场景中我们可以降低默认的缓冲区大小,这样可以支持更高的并发。

接收缓冲区设置原则

Linux也提供了参数:

cat /proc/sys/net/ipv4/tcp_rmem
4096    87380   4194304

用于设置接收缓冲区的动态调整范围,每个参数的意义与tcp_wmem类似。与发送缓冲区类似,也不能通过socket设置参数SO_RCVBUF,否则会关闭自动调节功能。默认值的设置与发送缓冲区也类似,高并发场景下可以调低其中。发送缓冲区的调节功能是自动开启的,而接收缓冲区Linux提供了参数

cat /proc/sys/net/ipv4/tcp_moderate_rcvbuf
1

默认值为1,表示开启接收缓冲区的动态调整功能。发送缓冲区自动调节的依据是待发送的数据,接收缓冲区由于只能被动地等待接收数据,它该如何自动调整呢?可以依据空闲系统内存的数量来调节接收窗口。如果系统的空闲内存很多,就可以把缓冲区增大一些,这样传给对方的接收窗口也会变大,因而对方的发送速度就会通过增加飞行报文来提升。反之,内存紧张时就会缩小缓冲区,这虽然会减慢速度,但可以保证更多的并发连接正常工作。
Linux提供了参数:

cat /proc/sys/net/ipv4/tcp_mem
3079776 4106368 6159552

用于设置可以用于TCP缓冲区的大小,当 TCP 内存小于第 1 个值时,不需要进行自动调节;在第 1 和第 2 个值之间时,内核开始调节接收缓冲区的大小;大于第 3 个值时,内核不再为 TCP 分配新内存,此时新连接是无法建立的,如果接收缓冲区满了,也无法接收新数据。需要注意的是tcp_mem是以page_size为单位(其值一般为4KB),而tcp_rmem与tcp_wmem是以Byte为单位。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值