从零开始学习TCP协议·中
目录前言1. 滑动窗口2. 流量控制3. 拥塞控制总结前言本文主要探讨TCP协议在保证可靠传输的基础上为了提高数据传输效率而引入的几大核心机制重点讲解滑动窗口、流量控制以及拥塞控制的原理。1. 滑动窗口由于确认应答与超时重传这两大核心机制在传递数据的可靠性上确实很有保障。人心是贪婪的所谓饱暖思淫欲此时开发者们就会想一个事儿如何变快一点不然传递效率太慢了。想想~客户端发送一句服务器回复一句。这样的传递方式是不是过于低效了。很多时间都浪费在等~上面了。于是开发者规定允许TCP在一定范围内可以不用收到ACK就可以直接发送下一条数据。这里的一定范围~就是滑动窗口的大小。假设客户端要给服务器发送一条1-10000长度的数据。此时的数据可以被分为这四个板块。[ 已发送已确认 ][ 已发送未确认 ][ 可发送 ][ 不可发送 ]。此时规定窗口大小为4000。客户端在窗口的限制下批量发送数据1-10001001-20002001-30003001-4000。此时窗口已经满了客户端必须等服务器的ACK回复使窗口移动后产生空余位置才得继续发送数据。服务器接收数据1-10001001-2000。并返回ACK。此时窗口开始滑动2000的数据被归到左边的已发送已确认窗口最左端是2001,窗口右端由原来的4000移动到6000。此时2001–4000这两组数据还在网络中游荡未被服务器接受成为了已发送未确认4001-6000这两组数据成为可发送。此时窗口空闲客户端发送数据4001-5000,5001-6000两组数据给服务器。服务器收到来自2001-3000,3001-4000.返回ACK窗口移动…这就是滑动窗口的运行机制。就好比大学里跑外卖的那群人以前是拿到一份外卖直接送给个人。现在是既然很多人的收货地是一样的那我就多拿几份一起送过去。中间就省下了很多配送的时间。这里我们还得讨论一个问题–丢包。这里分两种情况第一种是服务器返回的ACK丢了第二种是客户端发送的业务数据丢了。先看图片其实这并不要紧就算中间一个ACK丢了但只要服务器收到了主机B就没有问题。比如ACK 1001)这条丢失了但只要传过去的1001-4000这几组数据没有问题服务器返回ACK下一条件数据从4001开始客户端收到ACK发送4001…这就没有任何问题如果数据直接丢失了步骤就多一点。客户端连续发了包 1、包 2、包 3、包 4。包 2 在半路上丢了。包 1、3、4 成功到达。服务器收到包 1一切正常回复 ACK2期待包 2。服务器没等到包 2却先等到了包 3。它把包 3 放进缓冲区暂存回复 ACK2期待等包 2。服务器又等到了包 4。它把包 4 放进缓冲区再次回复 ACK2。客户端突然连续收到了 3 个一模一样的重复确认ACK2。客户端反应过来包2丢失了即刻补上包2。这个也叫做快重传Fast Retransmit有些地方也叫高速重发控制在短时间内收到连续3个重复的ACK明白某一段数据丢失了从而立刻补上。有别与超时等待超时等待是等待一段时间都没有收到ACK就说明丢包了2. 流量控制我们知道TCP协议传输过来的文件其实是先放入一个叫做缓冲区里面的。可以这样理解这个缓冲区就是一个杯子里面用来存储溶剂。如果溶剂太多就会溢出这就是丢包。所以滑动窗口的大小就不可以无限扩大。我这个杯子只有500ml的容量你一次性灌入700ml水多的水不就溢出了。还有要是我放水的速度还没有你灌水的速度快那不也溢出了。所以在服务器返回给客户端的ACK里面有一个叫做16位窗口大小的字段。里面就是记录你客户端可以连续发送的数据量。这个大小如何计算呢假设接收缓冲区的总大小有500ML,现在里面还盛放了之前来自客户端的数据300ML.那么此时服务器给客户端返回ACK的同时里面的报头中16位窗口大小的数值就是500-300也就是200ML。问题来了。如果返回的结果是0怎么办如果返回的是0也就意味着缓冲区被灌满了客户端不能在发送数据了。那什么时候知道缓冲区有余量了呢当缓冲区满后客户端会定期给服务发送一段没有载荷的数据包。这个数据包用来探测服务器的缓存空间是否还有余量。值得注意的一点是这个16位窗口位的大小可不一定是65536。得益于UDP踩过的坑TCP官方留了一手。在TCP报文格式中有一个叫做选项的部分。其中选项里有一个东西叫做窗口扩大因子M。窗口的实际大小为窗口字段的数值左移M位。3. 拥塞控制在互联网通信中主机A给主机B发送消息不是直接就发送过去的。而是要经过很多的转发器。中间这一坨就是数据要经过的路由器。在流量控制这一节我们讲过你批量发送的数据大小取决与接收方的缓冲区大小。那么中间这些路由器也是一样的。他们的缓存大小也决定了你的窗口大小。这么多的路由器那我们如何来确定窗口大小呢很简单直接把他们看成一坨。然后不断的去试探。比如你一开始发送1-2000的数据。一看欸对方收到了。那么你就增大数据量发送1-4000这样的数据量如果也通过了那下次就发1-8000的数据量以此类推…假设某一段时刻发现丢包了那你就重新发送1-2000的数据量过了就在增长…我们看看具体是如何增长的。看左边红色的部分这部分就是我们最开始说的增长可以明白一开始是指数增长的。看紫色部分ssthresh这个可以类比成我们JAVA标准库中hashmap里的那个负载因子。当增长的程度超过这个的时候就要变成线性增长了即拥塞避免阶段。也就是橙色的部分。如果线性增长到一定程度导致网络拥塞发生丢包了这里有两种不同的处理办法取决于丢包是如何被发现的慢启动Slow Start如果是因为“超时重传”发现丢包一直等不到ACK说明网络严重拥塞此时直接将拥塞窗口大小降为初始值通常为1同时ssthresh降为当前窗口的一半然后重新开始指数增长到达ssthresh后再线性增长。快恢复Fast Recovery如果是因为“快重传”发现丢包连续收到3个重复ACK说明网络虽拥塞但部分包还能传达此时将ssthresh降为当前窗口的一半并将拥塞窗口大小设置为这个新的ssthresh随后直接进入拥塞避免阶段继续线性增长。就和交友类似。你交了一个女朋友小美热恋期指数增长一段时间后平淡期感情线性增长。闹严重矛盾超时重传慢启动直接换个女朋友重新开启热恋期指数增长。小吵小闹收到3个重复的警告快恢复各退一步降降温继续慢慢过日子。至于为什么ssthresh的数值变了这个就像是第一口的可乐是最爽的后面几口爽感会降低。总结滑动窗口允许TCP在未收到ACK的情况下连续发送多个数据包利用空间换时间大幅提高传输效率。遇到丢包时可通过快重传机制迅速补发丢失数据。流量控制接收方通过ACK报文中的“16位窗口大小”字段告知发送方自身的接收能力防止发送方发包过快导致接收缓冲区溢出丢包。拥塞控制发送方探测网络拥塞程度动态调整发包速度。通过慢启动、拥塞避免、快重传和快恢复等算法避免给已经拥堵的网络环境雪上加霜。