~~~~~~~~~~~~~~~~~~~~

承上启下

又到了该承上启下,到此为止,我们叙述的TCP还都是简单的TCP,就算是简单的TCP,也存在上述的诸多问题,就更别提继续增加TCP的复杂性了。到此为止,我们的TCP都是端到端意义上的,然而实际上TCP要跑在IP网络之上的,而IP网络的问题是很多的,是一个很拥堵网络。不幸的是,TCP的有些关于确认和可靠性的机制还会加重IP网络的拥堵。

~~~~~~~~~~~~~~~~~~~~

5.IP网络之上的TCP

5.1.端到端的TCP协议和IP协议之间的矛盾

端到端的TCP只能看到两个节点,那就是自己和对方,它们是看不到任何中间的路径的。可是IP网络却是一跳一跳的,它们的矛盾之处在于TCP的端到端流量控制必然会导致网络拥堵。因为每条TCP连接的一端只知道它对端还有多少空间用于接收数据,它们并不管到达对端的路径上是否还有这么大的容量,事实上所有连接的这些空间加在一起将瞬间超过IP网络的容量,因此TCP也不可能按照滑动窗口流量控制机制很理想的运行。

势必需要一种拥塞控制机制,反应路径的拥塞情况。

疑难杂症15:拥塞控制的本质

由于TCP是端到端协议,因此两端之间的控制范畴属于流量控制,IP网络的拥塞会导致TCP分段的丢失,由于TCP看不到中间的路由器,因此这种丢失只会发生中间路由器,当然两个端点的网卡或者IP层丢掉数据分段也是TCP看不到的。因此拥塞控制必然作用于IP链路。事实上我们可以得知,只有在以下情况下拥塞控制才会起作用:

a.两个或两个以上的连接(其中一个一定要是TCP,另一个可以是任意连接)经过同一个路由器或者同一个链路时;

b.只有一个TCP连接,然而它经过了一个路由器时。

其它情况下是不会拥塞的。因为一个TCP总是希望独享整条网络通路,而这对于多个连接而言是不可能的,必须保证TCP的公平性,这样这种拥塞控制机制才合理。本质上,拥塞的原因就是大家都想独享全部带宽资源,结果导致拥塞,这也是合理的,毕竟TCP看不到网络的状态,同时这也决定了TCP的拥塞控制必须采用试探性的方式,最终到达一个足以引起其“反应”的“刺激点”。

拥塞控制需要完成以下两个任务:1.公平性;2.拥塞之后退出拥塞状态。

疑难杂症16:影响拥塞的因素

我们必须认识到拥塞控制是一个整体的机制,它不偏向于任何TCP连接,因此这个机制内在的就包含了公平性。那么影响拥塞的因素都有什么呢?具有讽刺意味的是,起初TCP并没有拥塞控制机制,正是TCP的超时重传风暴(一个分段丢失造成后续的已经发送的分段均被重传,而这些重传大多数是不必要的)加重了网络的拥塞。因此重传必然不能过频,必须把重传定时器的超时时间设置的稍微长一些,而这一点在单一重传定时器的设计中得到了加强。除此TCP自身的因素之外,其它所有的拥塞都可以靠拥塞控制机制来自动完成。

另外,不要把路由器想成一种线速转发设备,再好的路由器只要接入网络,总是会拉低网络的总带宽,因此即使只有一个TCP连接,由于TCP的发送方总是以发送链路的带宽发送分段,这些分段在经过路由器的时候排队和处理总是会有时延,因此最终肯定会丢包的。

最后,丢包的延后性也会加重拥塞。假设一个TCP连接经过了N个路由器,前N-1个路由器都能顺利转发TCP分段,但是最后一个路由器丢失了一个分段,这就导致了这些丢失的分段浪费了前面路由器的大量带宽。

5.2.拥塞控制的策略

在介绍拥塞控制之前,首先介绍一下拥塞窗口,它实际上表示的也是“可以发送多少数据”,然而这个和接收端通告的接收窗口意义是不一样的,后者是流量控制用的窗口,而前者是拥塞控制用的窗口,体现了网络拥塞程度。

拥塞控制整体上分为两类,一类是试探性的拥塞探测,另一类则是拥塞避免(注意,不是常规意义上的拥塞避免)。

5.2.1.试探性的拥塞探测分为两类,之一是慢启动,之二是拥塞窗口加性扩大(也就是熟知的拥塞避免,然而这种方式是避免不了拥塞的)。

5.2.2.拥塞避免方式拥塞控制旨在还没有发生拥塞的时候就先提醒发送端,网络拥塞了,这样发送端就要么可以进入快速重传/快速恢复或者显式的减小拥塞窗口,这样就避免网络拥塞的一沓糊涂之后出现超时,从而进入慢启动阶段。

5.2.3.快速重传和快速恢复。所谓快速重传/快速恢复是针对慢启动的,我们知道慢启动要从1个MSS开始增加拥塞窗口,而快速重传/快速恢复则是一旦收到3个冗余ACK,不必进入慢启动,而是将拥塞窗口缩小为当前阀值的一半加上3,然后如果继续收到冗余ACK,则将拥塞窗口加1个MSS,直到收到一个新的数据ACK,将窗口设置成正常的阀值,开始加性增加的阶段。

当进入快速重传时,为何要将拥塞窗口缩小为当前阀值的一半加上3呢?加上3是基于数据包守恒来说的,既然已经收到了3个冗余ACK,说明有三个数据分段已经到达了接收端,既然三个分段已经离开了网络,那么就是说可以在发送3个分段了,只要再收到一个冗余ACK,这也说明1个分段已经离开了网络,因此就将拥塞窗口加1个MSS。直到收到新的ACK,说明直到收到第三个冗余ACK时期发送的TCP分段都已经到达对端了,此时进入正常阶段开始加性增加拥塞窗口。

疑难杂症17:超时重传和收到3个冗余ACK后重传

这两种重传的意义是不同的,超时重传一般是因为网络出现了严重拥塞(没有一个分段到达,如果有的话,肯定会有ACK的,若是正常ACK,则重置重传定时器,若是冗余ACK,则可能是个别报文丢失或者被重排序,若连续3个冗余ACK,则很有可能是个别分段丢失),此时需要更加严厉的缩小拥塞窗口,因此此时进入慢启动阶段。而收到3个冗余ACK后说明确实有中间的分段丢失,然而后面的分段确实到达了接收端,这因为这样才会发送冗余ACK,这一般是路由器故障或者轻度拥塞或者其它不太严重的原因引起的,因此此时拥塞窗口缩小的幅度就不能太大,此时进入快速重传/快速恢复阶段。

疑难杂症18:为何收到3个冗余ACK后才重传

这是一种权衡的结构,收到两个或者一个冗余ACK也可以重传,但是这样的话可能或造成不必要的重传,因为两个数据分段发生乱序的可能性不大,超过三个分段发生乱序的可能性才大,换句话说,如果仅仅收到一个乱序的分段,那很可能被中间路由器重排了,那么另一个分段很可能马上就到,然而如果连续收到了3个分段都没能弥补那个缺漏,那很可能是它丢失了,需要重传。因此3个冗余ACK是一种权衡,在减少不必要重传和确实能检测出单个分段丢失之间所作的权衡。

注意,冗余ACK是不能捎带的。

疑难杂症19:乘性减和加性增的深层含义

为什么是乘性减而加性增呢?拥塞窗口的增加受惠的只是自己,而拥塞窗口减少受益的大家,可是自己却受到了伤害。哪一点更重要呢?我们知道TCP的拥塞控制中内置了公平性,恰恰就是这种乘性减实现了公平性。拥塞窗口的1个MSS的改变影响一个TCP发送者,为了使得自己拥塞窗口的减少影响更多的TCP发送者-让更多的发送者受益,那么采取了乘性减的策略。

当然,BIC算法提高了加性增的效率,不再一个一个MSS的加,而是一次加比较多的MSS,采取二分查找的方式逐步找到不丢包的点,然后加性增。

疑难杂症20:TCP连接的传输稳定状态是什么

首先,先说一下发送端的发送窗口怎么确定,它取的是拥塞窗口和接收端通告窗口的最小值。然后,我们提出三种发送窗口的稳定状态:

a.IP互联网络上接收端拥有大窗口的经典锯齿状

b.IP互联网络上接收端拥有小窗口的直线状态

c.直连网络端点间的满载状态下的直线状态

其中a是大多数的状态,因为一般而言,TCP连接都是建立在互联网上的,而且是大量的,比如Web浏览,电子邮件,网络游戏,Ftp下载等等。TCP发送端用慢启动或者拥塞避免方式不断增加其拥塞窗口,直到丢包的发生,然后进入慢启动或者拥塞避免阶段(要看是由于超时丢包还是由于冗余ACK丢包),此时发送窗口将下降到1或者下降一半,这种情况下,一般接收端的接收窗口是比较大的,毕竟IP网络并不是什么很快速的网络,一般的机器处理速度都很快。

但是如果接收端特别破,处理速度很慢,就会导致其通告一个很小的窗口,这样的话,即使拥塞窗口再大,发送端也还是以通告的接收窗口为发送窗口,这样就不会发生拥塞。最后,如果唯一的TCP连接运行在一个直连的两台主机上,那么它将独享网络带宽,这样该TCP的数据流在最好的情况下将填满网络管道(我们把网络管道定义为带宽和延时的乘积),其实在这种情况下是不存在拥塞的,就像你一个人独自徘徊在飘雨黄昏的街头一样...

5.2.4.主动的拥塞避免

前面我们描述的拥塞控制方式都是试探性的检测,然后拥塞窗口被动的进行乘性减,这样在接收端窗口很大的情况下(一般都是这样,网络拥堵,分段就不会轻易到达接收端,导致接收端的窗口大量空置)就可能出现锯齿形状的“时间-窗口”图,类似在一个拥堵的北京X环上开车,发送机发动,车开动,停止,等待,发动机发动,车开动...听声音也能听出来。

虽然TCP看不到下面的IP网络,然而它还是可以通过检测RTT的变化以及拥塞窗口的变化推算出IP网络的拥堵情况的。就比方说北京东四环一家快递公司要持续送快递到西四环,当发件人发现货到时间越来越慢的时候,他会意识到“下班高峰期快到了”...

可以通过持续观测RTT的方式来主动调整拥塞窗口的大小而不是一味的加性增。然而还有更猛的算法,那就是计算两个差值的乘积:

(当前拥塞窗口-上一次拥塞窗口)x(当前的RTT-上一次的RTT)

如果结果是正数,则拥塞窗口减少1/8,若结果是负数或者0,则窗口增加一个MSS。注意,这回不再是乘性减了,可以看出,减的幅度比乘性减幅度小,这是因为这种拥塞控制是主动的,而不是之前的那种被动的试探方式。在试探方式中,乘性减以一种惩罚的方式实现了公平性,而在这里的主动方式中,当意识到要拥塞的时候,TCP发送者主动的减少了拥塞窗口,为了对这种自首行为进行鼓励,采用了小幅减少拥塞窗口的方式。需要注意的是,在拥塞窗口减小的过程中,乘积的前一个差值是负数,如果后一个差值也是负数,那么结果就是继续缩减窗口,直到拥塞缓解或者窗口减少到了一定程度,使得后一个差值成了正数或者0,这种情况下,其实后一个差值只能变为0。

疑难杂症21:路由器和TCP的互动

虽然有了5.2.4节介绍的主动的拥塞检测,那么路由器能不能做点什么帮助检测拥塞呢?这种对路由器的扩展是必要的,要知道,每天有无数的TCP要通过路由器,虽然路由器不管TCP协议的任何事(当然排除连接跟踪之类的,这里所说的是标准的IP路由器),但是它却能以一种很简单的方式告诉TCP的两端IP网络发生了拥堵,这种方式就是当路由器检测到自己发生轻微拥堵的时候随机的丢包,随机丢包而不是连续丢包对于TCP而言是有重大意义的,随机丢包会使TCP发现丢弃了个别的分段而后续的分段仍然会到达接收端,这样TCP发送端就会接收到3个冗余ACK,然后进入快速重传/快速恢复而不是慢启动。

这就是路由器能帮TCP做的事。

6.其它

疑难杂症22:如何学习TCP

很多人发帖问TCP相关的内容,接下来稀里哗啦的就是让看《TCP/IP详解》和《Unix网络编程》里面的特定章节,我觉得这种回答很不负责任。因为我并不认为这两本书有多大的帮助,写得确实很不错,然而可以看出Richard Stevens是一个实用主义者,他喜欢用实例来解释一切,《详解》通篇都是用tcpdump的输出来讲述的,这种方式只是适合于已经对TCP很理解的人,然而大多数的人是看不明白的。

如果想从设计的角度来说,这两本书都很烂。我觉得应该先看点入门的,比如Wiki之类的,然后看RFC文档,793,896,1122等),这样你就明白TCP为何这么设计了,而这些你永远都不能在Richard Stevens的书中得到。最后,如果你想,那么就看一点Richard Stevens的书,最重要的还是写点代码或者敲点命令,然后抓包自己去分析。

疑难杂症23:Linux,Windows和网络编程

我觉得在Linux上写点TCP的代码是很不错的,如果有BSD那就更好了。不推荐用Winsock学习TCP。虽然微软声称自己的API都是为了让事情更简单,但实际上事情却更复杂了,如果你用Winsock学习,你就要花大量的时候去掌握一些和网络编程无关但是windows平台上却少不了的东西

6.1.总结

TCP协议是一个端到端的协议,虽然话说它是一个带流量控制,拥塞控制的协议,然而正是因为这些所谓的控制才导致了TCP变得复杂。同时这些特性是互相杂糅的,流量控制带来了很多问题,解决这些问题的方案最终又带来了新的问题,这些问题在解决的时候都只考虑了端到端的意义,但实际上TCP需要尽力而为的IP提供的网络,因此拥塞成了最终的结症,拥塞控制算法的改进也成了一个单独的领域。

在学习TCP的过程中,切忌一锅粥一盘棋的方式,一定要分清楚每一个算法到底是解决什么问题的,每一个问题和其他问题到底有什么关联,这些问题的解决方案之间有什么关联,另外TCP的发展历史也最好了解一下,这些都搞明白了,TCP协议就彻底被你掌控了。接下来你就可以学习Socket API了,然后高效的TCP程序出自你手!




相关内容