说不完的TCP

2018年8月4日15:48:17

我喜欢上微博,是因为这里有一群爱动脑筋的网友,常常激发有趣的讨论。

两周前我写了一篇《一个你本应该能解决的性能问题》,指出UDP的简单设计会带来一些问题,很快就有1000多人阅读。其中有位读者说,我就是用了TCP做远程镜像的传输,为什么还是很慢呢?后来我看了他的网络包,发现竟然有60多个TCP连接在抢带宽。要知道TCP的流控机制是作用于单个连接的,启用太多会导致流控形同虚设。举个例子,只启用一个连接的情况下发生了拥塞,那流控机制可以通过慢启动等方式,使网络得到恢复的机会。而有60多个连接在抢带宽的情况下,如果其中一个遭遇拥塞并进入慢启动,其他连接的持续发包也会导致拥塞难以缓解。

连接数量可以在Wireshark的Statistics菜单,Conversations选项中看到,如下图所示:

我分享了这位网友的案例后,又有很多人加入讨论,阅读量很快到了3000多。其中讨论最热烈的有3个问题:
1. 你说UDP的分片丢失会导致性能下降,那TCP为什么就不会有分片?
2. 为什么语音和视频等数据的传输都走UDP啊?
2. TCP的延迟太大了,我们正在开发基于UDP的可靠传输。

这三个问题之间没有关系,所以我们一个一个来分析。先看第一个问题,为什么用TCP就不会被分片?请看下图(摘自《Wireshark网络分析就这么简单》55页):

这是TCP三次握手的过程。10.32.106.169在第一个包声明了MSS=8960字节,而10.32.106.69在第二个包声明了MSS=1460字节。互相声明之后,双方都采用小的那一个值为标准,即都把网络包限制在1460+20(IP头)+20(TCP头)=1500字节以内。有了这个机制,被分片的概率就大大降低了。

UDP没有三次握手,所以都不知道对方的MTU是多少。因此应用层传下来的大块数据,只能交给网络层分片了。补救的办法也是有的,比如在采用UDP传输之前,先用其他协议探测最小MTU,然后在应用上把数据先切分好。不过根据我的经验,NFS等可以运行在UDP之上的应用层协议并没有采取这个补救措施。

接下来,再看第二个问题,为什么语音视频之类的数据就建议走UDP呢?这是因为它们不太在乎丢包,所以没有重传机制反而成了优势。你在音频聊天时说一声“我喜欢你!”这句话可能会被打成很多个UDP包发送。网络不好的时候丢掉一些包,通话质量会受到影响,但是对方不至于听成“我讨厌你!”之类的。视频也类似,模糊一点大家都可以接受,但假如采用TCP来确保每一帧都送达,可能会导致播放起来非常卡,反而难以接受了。本人不从事语音视频方面的工作,以上答案是当年刚毕业时一位面试官告诉我的。他是美国一个著名大学的计算机博士,应该有一定权威性。我凭经验也觉得很有道理。

好了,最后来看第三个问题——“
TCP的延迟太大了,我们正在开发基于UDP的可靠传输”。有位网友还提到,国外有公司已经开发出这样的产品,并卖了很多钱(上亿?如果我没有记错的话)。我个人觉得,开发一套基于UDP的机制来实现可靠传输,还不如优化TCP。现在TCP至少有Vegas, Westwood和Compound等选择,总有一款适合你,没有必要再去发明一遍轮子。况且TCP被认为延迟太大,本身就是一个误解。很多人是这样认为的:TCP因为发每个包都需要Ack,所以传一个包就要耗费一个往返时间(RTT)的时间,如下边左图所示。

事实并非如此。TCP可以同时发出很多包(称为一个发送窗口),然后同时确认,比如上边右图就是两个包一起发。接收方在确认的时候也不用每收到一个包就确认一次,理论上只要确认最后一个包就够了。比如下图所示,10.32.106.73一口气发出了10个数据包,被10.32.106.103用一个Ack=16148就确认完了。

上图这样还不算夸张的。在带宽无限,且启用了TCP Window Scaling的情况下,理论上可以1024M的字节同时发出去,然后等着确认。这样霸气的数据量,不用担心延迟吧?



  • 更新时间:2018年8月4日15:48:17 ,共 1634 字。