TCP流量控制中的滑动窗口大小、TCP字段中16位窗口大小、MTU、MSS、缓存区大小有什么关系

转自:https://www.zhihu.com/question/48454744


本文将涉及到IP、TCP、Socket缠绵悱恻的爱情故事,如果您依然相信爱情,请耐心地看下去…


TCP流量控制中的滑动窗口大小、TCP字段中16位窗口大小、MTU、MSS、缓存区大小有什么关系_第1张图片


MTU: Maximum Transmit Unit ,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小;以普遍使用的以太网接口为例,缺省MTU=1500 Byte,这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有> 1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。

MSS:Maximum Segment Size ,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。


A (MTU 1500) <------Internet ------> (MTU 1492) B

见上图,TCP SYN消息,A 发送给B 的MSS= 1460,告诉B,B发给A最大segment 为1460 byte

TCP SYN消息,B发送给A的MSS= 1452,告诉A,A发给B最大segment 为1452 byte 。

但A最终能一次发给B多大的字节的segment呢??? 我们给它取名为:A_Send_MSS,取决于两个值,一个是B的通告MSS= 1452;另一个是本地物理接口MTU的限制: 1500-20-20 = 1460。取这两者较小的一个值,则
A_Send_MSS = minimum ( 1452, 1460) = 1452

同理可得 
B_Send_MSS = minimum ( 1460,1452)=1452

可以看出这两者最后是相同的,所以可以得到以下结论,通信双方最终的MSS = 双方较小MTU- 40 。



Socket发送和接收缓冲区大小 :按照我的理解,socket将TCP实现细节封装成API,Socket虽然不等于TCP,但TCP的 send_buffer 和 receive_buffer 应该和socket 等同,用C语言的说法,就是指针相同,用通俗语言来说就是,相同的内存地址段。接收缓冲区receive_buffer 大小等同于自己通告的window size; 至于send_buffer 大小会影响application放入发送缓冲区的速率,但是它应该不是瓶颈,所以不再讨论它。


TCP Window Size : 如果A发送给B window size = 8192,意思是:B最多可以连续发送8192 byte 给A,在A ACK这8192 byte之前。那A的这个8192byte 怎么来的呢? 一般来说,8192byte就是A的接收缓区,A_Receive_Buffer= 8192,如果B不小心发送超过8192 byte,如果application没有及时取走,则超过8192 byte 数据可能会因为A_Receive_Buffer满而被丢弃,所以B会严格遵守A的 advertised window size,如果A通告的window = 0,则B一定不会发送数据。

Window Size 占两个byte,最大值为65535,看完下文你会发现它对对方发送速率影响很大。如果window size 是高速网络带宽的瓶颈,可以采用TCP Option scaling window 这个选项。


Persist Timer:  用于周期探测对方advertised window size 是否依然为0的定时器。接上文,如果A通告的window = 0,则B一定不会发送数据。那B会不会一直被动地等下去?大家想象一个这样的场景,如果B一直被动等待,而这时A接收缓冲区已经空了,但是B无法知道,既然A已经告诉我不能再发数据了,那我就乖乖听话不发了;A(女生)不会主动告知B(男生)哈哈,死锁了!

如何破?用 Persist Timer来解锁! 男生B一旦定时器超时,会发一个 Window Probe 消息给女生A,占一个字节,意思是:Are you ready? Do you still love me? 如果A依然很忙,发 window = 0 给男生B,意思是:本姑娘正忙,没空睬侬!男生B再Restart 自己的定时器,定时器超时再次骚扰女生A,一直会等到A发 window > 0 才结束,本姑娘空闲,欢迎随时来稿。于是男生欣喜若狂,继续发送数据,大段大段的情话…


Scaling Window:  位于TCP option里,在TCP SYN时发送scaling window,最大16,即2的16次方;如果对方没有此option,说明对方不支持,我方也要放弃使用。window size 与scaling window 两者相乘即得到TCP 的最大真实 window size = 64K* 64K= 4.29G bytes,比如我在国内发到美国的TCP流量,如果最大window size 是1 G 字节,一梭子就可以打出1G byte,我的1G的尾部数据还没有发完,我的先锋领头字节已经到达对方,然后对方就选择确认,对方已经刷新window size 了,意味着我方不需要停止等待,可以一直这么发送下去,这是一种提高TCP发送速率的一种高效手段。但使用scaling window 有前提的,适合 高速、长管道、丢包率极小


TCP建立连接的本质: A和B告知彼此的第一个发送字节的 初始序列号 ,建立连接后对每一个发送的字节都需要以初始序列号为 原点 进行编号,需要对方来 确认 每一个字节编号都已经成功接收,双方初始序列号是由 OS动态生成的,随机的值 ,一般每个TCP session都会有不一样的初始序列号,占四个字节。


滑动窗口( Slide Window): 为了便于描述,假定A和B的初始序列号都为0,A发1024字节的数据给B,B通告给A的window size = 4096。

对于A来说,最初的slide window 左侧是自己的原点,即初始序列号0,右侧就是 A的初始序列号+ B通告给A的 window size= 4096;

IF
A发出的1024 byte 数据如果没有得到B确认,A的slide window依然是静止的。

Else IF
A发出的1024 byte 数据如果得到B确认,B通告的 window = 3072,说明数据还在TCP接收缓冲区,application没有取走,则A的slide window左侧向右的方向移动1024,右侧依然静止是4096 ( 1024 + 3072)。

Else IF
A发出的1024 byte 数据如果得到B确认,B通告的 window = 4096,说明数据被application取走,则A的slide window左侧向右的方向移动1024,右侧移动到5120 ( 1024 + 4096)。

总结一下,A的slide window 左侧是自己发出,并被对方确认的字节序列号,右侧是左侧+ B通告window size。


TCP流量控制 :TCP三次同步握手建立连接之后,会 采用slow start 算法来快速摸到传输路径带宽的峰值 。为了便于叙述,假定window size 1024 byte = 1 segment,以下全采用segment,slow start 算法如下:

1)无论对方advertised window size 多大,A第一次只发一个TCP segment,即 congestion window =1,等待对方确认

2)收到确认,发2个 Congestion window = 2, 等待对方确认 

3)收到确认,继续加倍,发4个…



就这么指数翻倍,那是不是A可以无穷无尽地增长下去?当然不会,A的发送速率受到两个值制约:

一个是对方的 advertised window size  ,如果此值是8,那A最大一次只能发8个segment,需要等待B的确认,在A等待期间,A到B方向带宽是闲置的,需要至少等待一个 RTT(Round Trip Time),收到B的确认并且 window >= 1024 ,A才可以继续发送。如果网络带宽无限大,网络没有丢包,那带宽的峰值由对方的advertised window size 以及A与B之间RTT来决定。window size 越大,说明对方接收缓冲区越大,A可以最大限度利用带宽,可以让字节流充分占据链路,带宽闲置率低,发送速率也就越高;RTT越小,A的等待时间也相应小一些,但它对A发送速率的影响远没有window size来的大。

另外一个制约值就是 路径实时带宽 ,A方发送速率一直翻倍,终于到达了其最大峰值,此时的congestion window 被保存 到slow start threshold ,再大就开始丢包了,A方没有收到被丢弃包的确认,知道可能触发路径的拥塞,于是启动了另外一个法, congestion avoidance , 先将slow start threshold = 1/2 *slow start threshold,congestion window = slow start threshold +1 , 可以看出A方的发送速率立马就减到峰值的一半,于是以1/2的峰值速率 线性增长。何谓线性增长?就是收到对方确认,每次对 congestion window + 1,如果出现又丢包了,再将发送速率减半,然后在线性增长。这就是TCP特点,速率减少时,指数对半下降;速率上升时,slow start 是指数翻倍上升,而congestion avoidance 是线性增长。

总结一下,slow start虽然字面是慢启动,但是它可以指数翻倍增长,快速上摸到路径的带宽极限值,一旦有丢包,指数1/2下降,特点是大起大落,这不利于带宽的充分利用。

A方啥时采用slow start? 
A_Send_Window < Slow Start Threshold

而congestion avoidance 则是用来克服slow start 的不足,采用小碎步的方式,线性增长逼近带宽的峰值。

A方啥时采用congestion avoidance?
A_Send_Window > Slow Start Threshold

你可能感兴趣的:(网络编程)