📚 第六章 TCP与UDP
📄 一句话概括

TCP 的适用面是「怕丢不怕慢」:凡是内容不能缺一个字节的,都选它。 代价是「每连一次都要付一个往返」,所以长连接和连接池才那么重要。

📝 本节和前面几节的关系

6.4.10 讲的是「怎么用得更省」,这一节看「谁在用、为什么用」。 6.1.4 讲了选型标准,这一节把它落到具体协议上。

一、哪些应用跑在 TCP 上

应用 端口 为什么用 TCP
HTTP/HTTPS 80/443 少一个字节页面就坏
SSH 22 命令和输出必须按序
SMTP/IMAP 25/143 邮件不能丢字
FTP 20/21 文件要完整
MySQL 3306 语句和结果必须成套

五行其实是一个理由:「上层没有自己处理丢失的能力」。 网页不会自己重传,邮件客户端也不会,所以只能靠 TCP 兜住。 反过来,DNS 这种「一问一答」的协议就敢用 UDP—— 因为「没回就再问一次」这个逻辑太简单,不值得为它建连接、维护状态、还要处理半关闭。

二、握手成本有多高

一次 TCP 连接,在传数据之前要先付一个往返: 客户端发 SYN,服务端回 SYN 加 ACK,客户端再 ACK。 数据要等第三个包之后才能开始走。 同城几毫秒、跨省几十毫秒、跨洋一百多毫秒: 所以「打开一个国外网站慢」有一部分不是带宽问题,而是「握手就花掉了两三百毫秒」。 而 TLS 还要再来一到两个往返,所以「首次访问」的延迟是「握手加握手」。 这就是长连接和连接池存在的理由: 「连一次用很多次」把「每请求一个往返」摊薄成「每连接一个往返」。 HTTP/1.1 的 keep-alive、HTTP/2 的多路复用、数据库连接池,全都是同一个思路的不同实现。

三、实测与观察

本机连一个外网站点,握手耗时通常在几十毫秒量级: 这个数就是「纯网络往返」,不包含任何数据传输。 所以「同一个请求,第二次比第一次快」很常见—— 不是因为「缓存了数据」,而是「连接还在,不用再握手」。 这也解释了一个常见困惑: 「为什么用工具测速很快,但打开网页感觉慢」—— 测速测的是「大流量吞吐」,网页感受的是「很多次小交互的往返」。 两者的瓶颈完全不同。

四、动手 Demo:哪些应用跑在 TCP 上

把常见应用的端口和理由列出来,再实测一次握手耗时。 这个耗时就是「连接的成本」。

import socket, time

t0 = time.perf_counter()
s = socket.create_connection(('www.baidu.com', 443), timeout=5)
print('握手 %.2f ms' % ((time.perf_counter() - t0) * 1000))
s.close()

实跑输出:

--------------------------------------------------------------------
哪些应用跑在 TCP 上
--------------------------------------------------------------------
**TCP 的适用面是「怕丢不怕慢」**,**所以「凡是内容不能缺一个字节的」都选它。**

  应用                端口            为什么用 TCP
  -----------------------------------------------------------
  **HTTP/HTTPS**     **80/443**     **少一个字节页面就坏**
  **SSH**             **22**          **命令和输出必须按序**
  **SMTP/IMAP**      **25/143**     **邮件不能丢字**
  **FTP**             **20/21**      **文件要完整**
  **MySQL**           **3306**        **语句和结果必须成套**

**五行其实是一个理由**:**「上层没有自己处理丢失的能力」**。
**网页不会自己重传,邮件客户端也不会**,**所以只能靠 TCP 兜住。**
**反过来,DNS 这种「一问一答」的协议就敢用 UDP**——
**因为「没回就再问一次」这个逻辑太简单,不值得为它建连接。**

**实测一次 TCP 握手要多久**:
**连 www.baidu.com:443 用了 65.66 毫秒**,本地出口端口 57371。
**这几十毫秒就是「三次握手」的全部代价**:**一个往返用来协商,之后才开始传数据。**
**所以「短连接」的成本不只是带宽,还有「每连一次都要付一个往返」**——
**这就是 HTTP 长连接、连接池存在的理由。**

这个数字每次都不一样:它受路由、当前网络状况、DNS 缓存影响, 所以「测一次就下结论」是不靠谱的,要看多次的分布。 注意这里包含了「域名解析」的时间:create_connection 内部会先解析域名, 所以这个数比「纯 TCP 握手」略大。 要测纯握手,得先解析出地址,再连地址。 最后一个观察:「首包延迟」和「吞吐」是两个指标。 网页、游戏看首包延迟,下载、备份看吞吐,优化方向完全不同。 所以「网络优化」这句话必须说清「优化哪一项」。

30 秒实验

  1. 算一下:跨洋往返 200 毫秒,握手加 TLS 两个往返一共多少?
  2. 想一下:为什么「第二次请求更快」?
  3. 想一下:测速很快但网页慢,可能是什么原因?

常见误区

  1. 以为「TCP 应用都很慢」。 慢的是「握手和等待」,长连接之后差别不大。
  2. 以为「连接池只是省资源」。 它主要省的是「往返时间」,不只是内存。
  3. 以为「测速快就等于上网快」。 测的是吞吐,网页体验看的是「多次小交互的延迟」。
自测三问(点开对答案) 1. 为什么网页、邮件、数据库都用 TCP? —— **上层没有自己处理丢失的能力**,内容缺一个字节就坏了。 2. 一次 TCP 握手的成本是多少? —— **一个往返**:同城几毫秒,跨洋一百多毫秒。 3. 连接池主要省什么? —— **省掉「每请求一次握手」**,把往返成本摊薄到每条连接上。

下一节讲几个「非主流」的传输层协议。

相关笔记