TCP的报文可分为交互报文和成块报文,交互报文比较小,成块报文比较大。
以下出现的滑动窗口和CWND均以字节作为单位,为了讨论方便,故每次都会将其称为报文。
交互报文
交互报文就和日常的对话一样,你一句我一句,常用在交互性高的应用,如Rlogin远程登录,由于发送报文非常的频繁,在局域网中可能没有什么问题,但在广域网中,频繁发送报文可能加剧网络拥塞,因此,TCP提供了两种策略。
1、延迟发送ACK报文
当TCP接收到报文时,不会立刻发送ACK报文,一般会等待200ms,看看能不能在接收到本次TCP连接的报文,同时ACK多个报文(序列号连续的才会被ACK,例如先后接收到1000,1001,1009,则ACK为1001),当本机有数据要发送时,则会直接发送ACK报文,只是该报文的数据部分有要发送的数据,不会延迟发送ACK。
2、Nagle算法
当TCP含有未ACK的报文时,要发送的数据会缓存,直到接收到ACK,在将缓存的数据整理成一个报文发送出去,这个算法很有趣,若在网络不拥塞的地方,接收ACK的速度较快,发送报文的速度也快,每次发送的报文大小也比较小,在拥塞的WAN中,由于传送ACK比较耗时,所以每次发送的报文都比较大,减少了报文的发送频率。
Nagle算法的规则(可参考tcp_output.c文件里tcp_nagle_check函数注释):
(1)如果包长度达到MSS,则允许发送;
(2)如果该包含有FIN,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送,此时相当于禁用了Nagle算法;
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
TCP_CORK选项:设置该选项后,内核会尽力把小数据包拼接成一个大的数据包(一个MTU)再发送出去,当然若一定时间后,内核仍然没有组合成一个MTU时也必须发送现有的数据在某些情况下需要禁用Nagle算法,例如交互性要求很高的X Window System。
TCP滑动窗口:
TCP分为接收窗口和发送窗口
对于接收窗口而言,有以下几种情况:
1、窗口合拢。接收窗口的左边缘向右移动。在收到发送方的数据之后,接收方会确认数据的准确性,然后把数据存储到缓冲区中。需要注意的是:接收窗口只会确认连续的分组,对于乱序的分组,则是先接收下来,避免重复传送。 在确认连续的分组之后,因为数据还没有被应用进程取走,此时就需要进行窗口的闭合
2、窗口张开。窗口右边沿向右移动,说明允许发送更多的数据,这种情况发生在另一端的接收进程从TCP接收缓存中读取了已经确认的数据时;
3、窗口收缩。窗口右边沿向左移动,一般很少发生,RFC也强烈不建议这么做,因为很可能会产生一些错误,比如一些数据已经从缓冲区发送给应用程序了,又要收缩窗口,不让发送这些数据。
左边缘往右移动表示确认的序列号报文占用了缓冲区的空间,右边缘往右移动表示已确认报文占用的缓冲区空间被释放,当左右两边重合,窗口大小为0,无法在接收报文,此时发送方不会发送报文,左边的边就不会往右移动,超过右边的边。滑动窗口协议可以让接收方不用ACK每个报文,由于具有缓冲区,所以可以一次性ACK顺序的报文(例如:同时接收序列号为1001,1002,1003的报文,发送ACK为1003的报文)。
对于发送窗口而言,其由两部分构成:已发送但未确认,未发送但允许发送,发送窗口是根据接收方的接收窗口大小确定的,当收到一次ack时,窗口的左边缘往右移动,当左边缘停止移动时,在移动右边缘,使当前窗口大小为接收方窗口的大小
滑动窗口的作用是什么?
谈谈我的理解:滑动窗口作用为流量控制,控制发送方发送报文的速度,让接收方有时间处理报文,减少网络拥塞,发送方一次发送报文总字节数不能超过滑动窗口大小,拿开车做比喻,就是不能超速。假如没有滑动窗口,那么发送方一次可以发送非常多的报文,就可能早成网络拥塞。
由于报文可以乱序到达,可能出现以下情况:
1001到达,1003以后的报文均到达,但由于1002未到,此时通告的滑动窗口的大小其实远大于实际缓冲区的大小,会不会出现缓冲区溢出的情况?
这里要搞清楚滑动窗口与内核缓冲区的区别:
整个数据的流程中,首先网卡接收到的数据存放到内核缓冲区内,然后内核缓冲区存放的数据根据TCP信息(可能是根据端口号和IP地址)将数据移动到具体的某一个TCP连接上的接收缓冲区内,然后应用程序从TCP的接收缓冲区内读取数据,如果应用程序一直不读取,那么滑动窗口就会变小,直至为0,滑动窗口的值不一定就是TCP接收缓冲区的值
如果网卡处理数据的速度比内核处理数据的速度慢,那么内核会有一个队列来保存这些数据(在发送数据时会出现这种情况)
我的理解是滑动窗口与TCP缓冲区没有任何关系,只是起到流量控制作用
另外,滑动窗口表示的是TCP数据段的长度,不包括TCP的报文头
TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”,“发送窗口”就是对方的滑动窗口大小,“接收窗口”就是本机的滑动窗口大小,TCP传送报文速度不能大于应用程序处理报文的速度,各自的“接收窗口”大小取决于应用、系统、硬件的限制。
PUSH报文
将TCP头部的PSH位置为1即为PUSH报文,PSH位就是用来通告接收方立即将收到的报文连同TCP接收缓存里的数据递交应用进程处理.一般会出现在发送方封装最后一个应用字段的TCP报文中,针对TCP交互式应用,则只要封装有应用字段的TCP报文,均会将PSH位置1.接收端TCP协议栈收到PUSH位置1的报文段,就尽快地的将缓冲区中的数据交付给接收应用进程,而不再等到整个缓冲都填满了再向上交互
问题:在接收缓冲区中的数据可能不是连续的,此时利用PUSH将数据传给应用程序,应用程序会处理不连续的数据流部分吗?
以下为本人的想法
答案是不会,有PUSH标记的数据可能需要与序列号比自己小的数据共同构成一个完整的信息,或者TCP协议栈的处理可以保证不会出现将乱序的数据交给应用程序处理的情况。
现在的TCP协议栈基本上都可以自行处理何时发送PUSH报文,而不是交给应用层处理.
TCP防止拥塞产生的机制
发送方往往会发送多个报文,在WAN中,稍不留神就会产生网络拥塞,在路由器上会缓存待转发的报文,若报文传输过快,则多余的报文会被丢弃,造成重传,上网时就会感到明显的网络延迟,为了解决这个问题,出现了慢启动算法。
慢启动
通过缓慢增加发送数据包的数目来减少网络拥塞的产生。
慢启动多了一个拥塞窗口(简称CWND),当建立新的连接时,CWND初始化为1,此时只能发送一个报文,每次接收到一个ACK,CWND都会加自己,发送方可以发送的报文数目为滑动窗口、CWND中的较小者。
拥塞避免
CWND的增长不能是没有限制的,为此设置了慢启动门限的值,一般这个值被设置为65536,当CWND超过这个值时,由慢启动进入拥塞避免阶段,此时CWND的值置为1,当CWND大小的字段得到确认后,CWND值加一,(例如现在CWND的大小为1000,当1000大小的字段被ACK后,CWND值加1)。
TCP为每一个报文都设置了一个重传定时器(RTO),当RTO到期还未收到ACK,采取的措施如下:
1、将慢启动门限的值降为原先的一半。
2、将CWND的值置为1.
3、重新进入慢开始状态。
快速重传:
要求接收方每次接收到对方发送的失序报文就发送一个ack,当发送方收到三个重复确认的报文时,就立刻重传
快速恢复
在快速重传之后添加了快速重传算法,在收到三个重复的ACK之后,TCP进入快速恢复阶段,快速恢复遵循管道原则,即在传输中的报文数量守恒原则,具体步骤如下:
1、当接收到三个重复的ack时,将慢启动门限的值设为原先的一半,将CWND的值设置为慢启动门限的值加3(无论何时TCP接收到一个序列号超过ACK的报文,都会回应一个值为期望序列号的ACK),加三是因为收到三个重复的ACK,意味着有三个报文离开了网络,表示自己可以在发三个报文,来维持报文数量守恒。
2、在收到重复的ack,CWND的值加1,原因是又有一个报文离开了网络,所以加一,
3、当收到新的ACK时,将CWND的值置为慢启动门限的值,随即进入拥塞避免阶段。