本节书摘来自异步社区《Wireshark网络分析的艺术》一书中的被误解的TCP,作者林沛满,更多章节内容可以访问云栖社区“异步社区”公众号查看。
被误解的TCP
Wireshark网络分析的艺术
人一旦形成某种思维定势,就很难再改变了。知道我收到最多的读者来信是问什么吗?“林工,有些TCP包发出去之后没有看到对应的Ack,算不算丢包啊?”这个问题让我很是好奇,明明RFC上没有这样的规定,为什么总有读者觉得每一个数据包都应该有对应的Ack呢?后来才注意到,很多提问者是做网站开发出身的,已经习惯了每个HTTP请求发出去,就一定会收到一个HTTP响应(见图1),因此就把这个模式套到了TCP上。其实不止HTTP,绝大多数应用层协议都采用这种一问一答的工作方式。
TCP当然也可以采用这种方式,但并非必要。就像我们不用每天都跟公司算一次工钱,而是攒到月底结算一样,数据接收方也可以累积一些包才对发送方Ack一次。至于Ack的频率,不同的操作系统有不同的偏好,比如我实验室中的Linux客户端喜欢每收到两个包Ack一次,见图2。
而Windows客户端则懒得多,隔好多个包才Ack一次,见图3的97号包。
这两种方式都是正常的,但Linux对流量更“大手大脚”一点,因为纯Ack也算流量的。其实在网络带宽越来越大的今天,人们已经不在乎这种小流量了。不过手机操作系统还是要慎重考虑的,毕竟蜂窝数据是按流量计费的,能省一点是一点。我的安卓手机就是每收到一个包都会Ack的,想到这里我的心都在滴血。图4是我在微博上打开一张美女图时产生的流量,你看这些密密麻麻的纯Ack,每个都白费我40字节的流量。
也许以后会有手机厂商优化它,然后以此作为卖点。如果是从我这本书里学到的,请为它命名“林朗台算法”。
既然接收方不一定收到每个包都要Ack,那发送方怎么知道哪些包虽然没有相应的Ack,但其实已经送达了呢?记住,Ack是有累积效应的,它隐含了“在此之前的其他包也已收到”的意思,比如图3中第97号包的Ack=65701不仅表示收到了96号包(其Seq+Len=64273+1428=65701),而且暗示之前的其他包也都收到了。因此86~95号包虽然没有被显式Ack,但发送方知道它们也已经被送达了。
另一个对TCP的广泛误解则和UDP相关。有不少技术人员认为TCP的效率低,因为其传输过程中需要往返时间来确认(Ack)。而UDP无需确认,因此能不停地发包,效率就高了。事实真的如此吗?这其实是对TCP传输机制的严重误解。我们可以假设一个场景来类比TCP的工作方式:有大批货物要从A地运往B地。如果只用一辆货车来运的话,马路上就只有一辆车在来回跑(回程相当于TCP的Ack包),效率确实很低,对TCP的误解可能也出自这个原因。但如果在不塞车的前提下尽量增加货车数量,使整条马路上充满车,总传输效率就提高了。TCP发送窗口的意义相当于货车的数量,只要窗口足够大,TCP也可以不受往返时间的约束而源源不断地传数据。这就是为什么无论在局域网还是广域网,TCP还是最受欢迎的传输层协议。
当然TCP确实也有因为往返时间而降低效率的时候,比如在传输小块数据的场景。本来能在1个往返时间完成的小事,却要额外耗费3次握手和4次挥手的开销,DNS查询就符合这种场景。目前HTTP基本建立在TCP连接上,所以也会因为TCP的三次握手而增加延迟。你可能听说过Google发布的QUIC(Quick UDP Internet Connection)协议,它就是为了消除TCP的延迟而设计的代替品。在某些领域可以视为TCP的竞争对手,目前在Google的网站上已经可以试用了。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。