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

认一条连接要五样:协议、源地址、源端口、目的地址、目的端口。 少一样就会撞车:两台机器同时访问同一个网站,只靠 IP 分不开。

📝 本节和前面几节的关系

6.2.2 讲了「端口认应用」,这一节讲「五个要素合起来才认连接」。 5.6.2 的 NAT 表用五元组当粒度,根子就在这一节。

一、五个要素

这五样凑齐,才唯一确定一条连接:

要素 在哪一层 作用
协议 传输层 TCP 还是 UDP
源地址 网络层 谁发的
源端口 传输层 哪个程序发的
目的地址 网络层 发给哪台机器
目的端口 传输层 发给哪个程序

「协议」为什么不能省:同一个端口号在 TCP 和 UDP 上是两个独立的位置(6.2.5 会验证), 所以「53 端口」可以是两个不同的服务。 不写协议,就分不清是哪一个。 有些书写「四元组」,是不算协议号的:因为「一条 TCP 连接」的语境里协议已经确定了。 所以「四元组」和「五元组」说的是同一件事,只是口径不同——写文档时要说清用的是哪个口径。

二、为什么必须五个

少一个会怎样,可以逐个试:

少了谁 什么会撞车
少源端口 同一台机器开两个网页分不开
少目的端口 访问同一台服务器的两个服务分不开
少源地址 NAT 后面多台机器分不开
少协议 TCP 53 和 UDP 53 分不开

第三行是 NAT 的核心难题:一个公网地址背后有几十台机器, 如果只看地址,回程的包不知道给谁。 所以 NAT 必须改「源端口」来区分,这就是「网络地址端口转换」(NAPT)这个名字的由来。 第四行是「两个服务共用端口号」的场景:DNS 同时开 TCP 53 和 UDP 53 是标准做法, 看流量时如果只说「53 端口」,就分不清是哪一种。

三、同一对主机的多条连接

最典型的场景是浏览器:打开同一个网站的十个页面,就有十条连接, 源和目的 IP 完全相同,目的端口也都是 443。 区别只在「源端口」——十条连接十个源端口。 所以「一条连接」的粒度细到「端口对」,而不是「主机对」。 这一点在设计任何「连接表」时都要记住:只按 IP 建索引的表,一定会撞。 反过来也有好处:因为源端口不同,多条连接可以各自独立地「拥塞控制」, 所以「同时开十个下载」有时比「开一个」更快——虽然这并不总是好事(会把别人挤掉)。

四、动手 Demo:一条连接靠五元组认出来

从本机取几条已建立的连接,看它们的五元组长什么样。 注意「本地」那一列的端口都是高位。

import psutil, socket

for c in psutil.net_connections(kind='inet'):
    if c.status == 'ESTABLISHED' and c.raddr:
        p = 'TCP' if c.type == socket.SOCK_STREAM else 'UDP'
        print(p, '%s:%d' % (c.laddr.ip, c.laddr.port),
              '->', '%s:%d' % (c.raddr.ip, c.raddr.port))

实跑输出:

--------------------------------------------------------------------
一条连接靠五元组认出来
--------------------------------------------------------------------
**协议、源地址、源端口、目的地址、目的端口**——**这五样凑齐,才唯一确定一条连接。**

  协议     本地                                             对端
  -----------------------------------------------------------------------------------
  TCP      127.0.0.1:49677                                  127.0.0.1:49676
  TCP      192.168.3.152:49742                              23.11.38.161:80
  TCP      192.168.3.152:56889                              27.21.227.177:443
  TCP      127.0.0.1:49853                                  127.0.0.1:57163
  TCP      240e:36c:374b:7c01:288a:4300:1ddc:1916:57380     240e:97d:10:2500::18:443
  TCP      240e:36c:374b:7c01:288a:4300:1ddc:1916:57168     240e:97d:10:2500::17:443

**注意「本地」那一列的端口都是高位**:**它们是临时端口,每条连接一个**,
**所以「同一台电脑访问同一个网站十次」会有十个不同的源端口。**
**这就是 5.6.2 里 NAT 表要用「五元组」当粒度的原因**——**只看 IP 和端口对,分不清是哪一条会话。**

**如果只按「源 IP 加目的 IP」区分**:**同时开两个网页就会撞车**,**所以「端口」这一维不能省,**
**它承担了「同一对主机之间的多条会话」的区分工作。**

「本地」那列全是 5 万多端口:它们是临时端口,每条连接一个, 所以「同一台电脑访问同一个网站十次」会有十个不同的源端口。 这也解释了「为什么同一个目的地址可以有几百条连接」:因为源端口不同,五元组就不同。 如果只按「目的地址」去重,会以为只有一条。 反过来说,「同一对端口」也能有多条连接吗? 不能——如果源端口、目的端口、地址、协议全都一样,那在系统看来就是同一条连接。 所以「五元组唯一」是硬约束,不是约定。

30 秒实验

  1. 想一下:为什么「协议」这一项不能省?
  2. 算一下:一台机器连同一个网站,最多能建多少条连接?
  3. 想一下:NAT 为什么必须改源端口?

常见误区

  1. 以为「四元组就够了」。 在「一条 TCP 连接」的语境里够,但跨协议比较时必须带上协议号。
  2. 以为「同一对主机的连接只有一条」。 浏览器一个页面对一个站就几十条,靠源端口区分。
  3. 以为「五元组只是理论」。 NAT 表、防火墙会话表、netstat 全按它建,是实打实的实现基础。
自测三问(点开对答案) 1. 五元组是哪五样? —— **协议、源地址、源端口、目的地址、目的端口。** 2. 为什么必须靠源端口区分多条连接? —— **同一对主机访问同一个服务时,地址和目的端口都一样**,只有源端口不同。 3. 「四元组」和「五元组」的区别? —— **四元组不算协议号**,因为语境里协议已定;**跨协议比较时必须写五元组。**

下一节讲「端口号到底是谁定的」。

相关笔记