一. 缓冲区
TCP协议是一个全双工的协议,当A与B建立好连接后,可以互相发送数据,当A作为发送方时,存在一个发送缓冲区,也就是说发送的数据会先放置在发送缓冲区处,而作为接收方的B会有一个接收缓冲区,接收到的信息会先存储在接收缓冲处。
发送窗口,则是发送缓冲区的一部分。
二. 滑动窗口
发送数据时通常是按字节发送,下面来看一张图:
我们知道,TCP是一个可靠的协议,当A发送完一部分数据后,需要接收方回B发一个确认数据包(ACK),这样,A才能继续发送接下来的数据,这也就是图中显示的情况,位于缓冲区中的窗口,首先,是已发送并收到确认的报文,P1 - P2是A已经发送出去但还未收到B回发确认数据包,此时,A将等待B发送的ACK数据包,P2 - P3为允许发送但尚未发送的数据,需要等待之前的数据的到确认这一部分数据才能够发送。
假设此时,从P1开始的3个字节发送成功(发送成功表示发送并且收到ACK确认数据包),则窗口将会滑动:
可以看到,第31,32,33个字节已得到确认,窗口整体向右移动3个字节,第51,52,53字节从不允许发送变为允许发送。
每次发送成功后,发送窗口便会在缓冲区中按顺序移动,将新的数据包含至窗口中准备发送
三. 流量控制
当A,B建立连接,进行数据发送时,需要结合实际情况来控制发送流量,假设B数据处理不过来,或者说缓冲区已满,A则不能继续向B发送数据,这个关于B的缓冲区的信息,将会包含在ACK数据包中来告知A,当B的接收区窗口大小rwnd=0时,A将不再发送,知道B的接收窗口空闲了,B再给A发送通知,这样变控制了流量。
有一种情况:
B的接收窗口满后,回发给A告知暂停发送,A处于阻塞等待,当B处理完数据给A发送数据包时,这个数据包丢失了,那这就会出现死锁的问题:A在等待B,B也在等待A,于是便存在一个persist定时器(持续定时器),顾名思义,也就是说A会间隔固定时间后尝试的发送一个数据,探测B是否可以接收数据,若B回发了ACK数据包,则又进入了之前的工作状态。
四. 传输效率
想象一下,当发送数据时每次都只发送一个或几个几字,而TCP的头就占据了40个字节,很明显这样的发送效率是极低的,这就要涉及到nagle算法了。
nagle算法:尽可能的多发送几个字节,避免网络因为太多的小包(协议头的比例非常之大而拥塞),因此,只允许一个未被确认的ACK报文存在于网络。怎么理解最后一句话呢,也就是说发送了一个数据包在没有接收到ACK确认之前,不允许在发送新的数据包。nagle算法包括以下特点来提高传输的效率:
- 如果长度达到MSS,则允许发送
- 如果数据包中包含FIN(断开连接标致),则允许发送
- 当设置了TCP_NODELAY选项,则允许发送,禁止Nagle算法
- 未设置TCP_CORK选项时,若所有发送的小数据包(包含长度小于MSS)均被确认,则设置该选项后,内核会尽力把小的数据包拼成一个大的数据包(一个MTU)在发送出去,若到指定时间,一般为200ms,仍未组成一个MTU,也要立刻发送。
int on = 1;
setsockopt(fd,SOL_TCP,TCP_CORK,&on,sizeof(on));
- 上述情况都不满足,但超时(200ms)则立即发送
- 接收方等待一段时间,或者接收方获得足够空间容纳一个报文或者接收缓冲区有一半空闲,再通知发送方发送数据