TCP滑动窗口如何实现流控?

TCP流控的最终目的就是要让发送端发送的数据量与接收端的实际接收能力(可用接收缓冲区的大小)相匹配,防止接收方被到来的数据流淹没(没有能力再接受新的数据,进而将其丢弃)。

“淹没”即,当发送方的发送速率高于接收方的受理速率的时候,接受方的接收缓存就会长期处于饱和状态,新到来的数据没地方存放就只能丢弃掉。发送端收不到ACK,就进行重传,重传的数据同样又被收端丢弃,发端再次重发,这样就陷入了一个恶性循环。

通常说的窗口,其大小是固定,所以,普通的“滑动窗口”是无法实现流量控制的。TCP所采用的“滑动窗口”实际上是一种改进版本,其窗口大小是可以动态改变的。

在TCP通信过程中,窗口尺寸由接收方通告给发送方,发送方据此改变自己的发送窗口大小,实现流量控制 ...


数据流与数据报

数据(字节)流
Data stream
数据报
Datagram
单位  字节   数据报 
边界  连续字节流,无边界   每个数据报都有自己的边界 
结构  无结构   有结构 
发送  逐字节或将连续字节合并成数据块发送   逐个发送 ...


逐步实现TCP服务端Step02-3:继续改进

经过改造,主流程中的RecvBytes、GetOneMessage和SendBytes都不会引起阻塞。只有ProcessOneMessage存在阻塞的可能。

ProcessOneMessage中有潜在的“长操作”,例如:磁盘I/O、大量的计算等。若服务端在Process一个消息的时候进行了这种“长操作”,那么主流程就没法快速的进行下去了。

考虑到数据的收发都是在这个主流程成中完成的。Process ...


逐步实现TCP服务端Step02-2:改进

到目前为止服务端处理消息的情况是这样的:

先从socket缓冲中尽力取字节,把取到的字节放到一个单字节数组中去(recv字节队列)。然后,从这个数组中取字节,组成消息。

每次要发送消息时,都要先尝试发送post字节队列中残留的字节,然后再去发送本次实际要发送的字节。如果此次发送没有将期望的字节量全部发出,就把剩余的字节存到post队列中,等待下次调用SendOneMessageEx时再尝试发送。

发送字节的操作是在SendOneMessageEx中进行的,如果SendOneMessageEx不被调用的话,残留的字节就没机会被发送。这个地方不太合理,说白了SendOneMessageEx也应该拆成两个过程 ...


逐步实现TCP服务端Step02-1:基于非阻塞socket的处理逻辑

把socket设置成非阻塞,其目的就是不让程序逻辑在网络I/O处阻塞不前。整个程序都将基于这个“不阻塞”的大前提来进行设计。基于非阻塞socket调用recv和send时不会阻挠逻辑向前进行,那么,其包装函数RecvOneMessage和SendOneMessage也要做到不阻挠才合适。否则,若使用这组包装函数去替换原始函数,程序的行为就有可能会偏离我们的预期。

RecvOneMessage函数内部是循环操作,达到目标有可能要进行多次循环。同时,对于阻塞式socket来说,一次循环中的recv调用又有可能发生阻塞。这么看来基于阻塞式socket的RecvOneMessage函数是有可能阻挠逻辑前进的。

那么,基于非阻塞socket的情况呢 ...


逐步实现TCP服务端:index