一、滑动窗口
之前我们学习了确认应答策略,对每一个发送的数据段都有一个ACK确认应答,为了提高效率,普遍情况是一次发送多条数据:
滑动窗口大小
是指无需等待确认应答而可以继续发送数据的最大值,滑动窗口其实是发送缓冲区的一部分, 其左边就是已经发送并且已经被确认的数据,已经无效了,因此这部分空间可以被重新利用了,但不需要刻意地去清空缓冲区。
序号在发送的轮次中,数字是依次增大的,这也就意味着滑动窗口宏观上看是向右移的。而滑动窗口移动的本质是:start和end下标的增加。那么滑动窗口的大小是由谁来决定的呢? 由对方的接收能力即收到对方报文中的窗口大小决定(暂时这么理解),因此滑动窗口还是流量控制的具体实现方案。这么看,start就是报文确认序列号,end就是start+窗口大小。
1.滑动窗口可以向左滑吗?不可以。
2.滑动窗口可以变大吗?可以,当对方接受缓冲区的数据被取走后,窗口就会变大,同理,滑动窗口也可以变小,可以为零。
那么如果丢包了滑动窗口会跳过报文进行应答吗?以上图的①为例:
如果是滑动窗口左侧丢失(其余情况都能转换为最左侧丢失),会由以下两种情况:
1.最左侧的数据(报文)丢了的话,哪怕对方收到了后续数据,也只会返回1001,也就是说滑动窗口左侧是不变的
2.如果只是最左侧的应答丢了的话,滑动窗口就会正常工作,因为后续报文的应答如2001,3001,4001等都代表着2001以前的数据已经被收到了
如上图收到三次1001后进行重传的机制被称为快重传
,快重传用来提高效率,其触发条件是三个同样的确认应答,而超时重传
的触发条件是超时+没有应答,超时重传是为快重传兜底的,两者同时存在。 为了重传,滑动窗口在收到应答前不会向右滑动,这样数据就不会丢失,只有确认收到应答后再向右滑动。
那么滑动窗口一直向右滑动,会溢出吗?其实我们可以将上述的字符数组看成一个环形数组,这样滑动窗口左边已经无效的数据就能再次得到应用。
二、流量控制
接收端处理数据的速度是有限的,受两个因素的影响:分别是应用层处理数据的速度和接收缓冲区的大小。如果发送端发的太快,就会导致接收缓冲区被打满,继续发送就会造成丢包等一系列问题。因此TCP支持根据接收端的处理能力来决定发送端的速度,这个机制就叫做流量控制
。
接收端通过 TCP 首部中的窗口大小
字段向发送端通告其可接收的缓冲区容量。窗口大小直接反映了网络吞吐量,数值越大表示网络传输能力越强。当接收端检测到缓冲区即将满载时,会主动减小窗口大小并通知发送端。发送端收到调整后的窗口信息后,会相应降低数据发送速率。若接收端缓冲区完全饱和,则会将窗口值置为 0,此时发送端暂停数据传输,但仍需定期发送窗口探测报文,以便及时获取接收端的最新窗口状态。而最开始双方建立连接时,三次握手中前两次是不带数据的,这两次握手已经互相交换彼此的通信能力了,因此其实第三次握手时是能直接携带数据构成捎带应答的。
但是16位窗口大小只有16位,该数字最大为65535,难道TCP窗口最大就是65535字节吗?实际上,在TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。
三、拥塞控制
TCP不仅仅考虑了双方主机的问题,还考虑了网络本身的问题,当然这是在硬件无障碍的前提下。当发送的报文出现大面积丢失时,发送方就要判定为网络拥塞
的问题,这时就不能立即重发报文了,如果立即重发势必会增加网络的负载,使得网络更加拥堵。
若判定出现网络拥塞,TCP引入慢启动
机制,先发少量的数据,来探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据,若发送的数据都接收到了,就意味着网络不怎么拥堵了,然后就能加快发送速度了,像下图这种增长速度就是指数级的,初始时慢但增长速度非常快:
为了支持拥塞控制算法,设计一个int类型的拥塞窗口
,其实就是一个临界值,超过该值就可能发生拥塞,是衡量网络是否会拥堵的一个指标,而网络是变化的,因此拥塞窗口也一定在变化。滑动窗口实际的大小其实是对方窗口大小和拥塞窗口大小的较小值,这意味着不能只看对方的接受能力,还必须考虑网络的因素。
而拥塞窗口的值也并不是一直指数增长的,而是到了临界点后转为加法增大
,前面的指数增长是为了解决拥塞,恢复网络通信,而后的线性增大则是为了不断探测网络新的拥塞窗口值。当探测到出现网络拥塞后就又重新慢开始探测网络健康,新的ssthresh值
是上一次最大出现网络拥塞的值除以2,到达新的ssthresh值后再开始线性增长。
四、延迟应答
如果接受数据的主机收到报文后立即返回ACK应答,此时返回的窗口可能比较小,因为可能接受缓冲区中马上就要有数据被拿走了;窗口越大,网络吞吐量越大,传输效率越高,我们的目的是保证网络不拥塞的情况下尽量提高传输效率,那么如果让接受方收到报文后等一会儿再应答,就有很大概率等到一个更大的窗口大小,这样对方也能更新出一个更大的滑动窗口,效率就得到了提高,这种机制就是延迟应答
机制。
当然也不是每一次延迟应答都能让窗口大小变大,只是存在较大概率而已,当样本大小足够大时,就一定能够提高效率。
那么所有的包都可以延迟应答吗?肯定也不是,存在数量限制
和时间限制
,每隔N个包就应答一次和超过最大延迟时间再应答。一般N取2,时间取的200ms,不同系统策略也不同。
五、粘包问题
由于TCP是面向字节流的,因此存在粘包问题。而UDP存在明确的数据边界,UDP要么收到完整的UDP报文,要么就不收,不会出现半个的情况。
解决粘包问题跟传输层的TCP是没关系的,TCP是一个一个报文过来,按照序号排好放在接受缓冲区中,需要解决粘包问题的是应用层,如何解决?其实我们早就做过了,就是自定义协议序列化和反序列化而已。
那么对于应用层而言,如何避免粘包问题呢?对于定长的包,可以每次按固定大小读取即可。对于变长的包,可以在包头的位置约定一个包总长度的字段,从而知道包的结束位置;当然也可以在包与包之间使用明确的分隔符。
六、TCP异常情况
当进程终止时,文件描述符会释放,但此时内核据此发送FIN并自动处理后续挥手,因此四次挥手仍能完成。
若机器掉电/网线断开,接收端认为连接还在,一旦接收端有写入操作,就会发现连接已经不在了,此时就会进行reset重连。即使没有写入操作,TCP自己也会内置一个保活定时器
,会定期询问对方是否还在,若对方不在了也会把连接释放。