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

端口号属于「协议」而不属于「主机」:TCP 53 和 UDP 53 是两个不同的位置。 所以「端口被占用」只在同一个协议内成立,跨协议各占各的。

📝 本节和前面几节的关系

6.2.4 讲端口怎么分配,这一节讲「这个分配是分协议做的」。 6.6 和 6.7 会逐字节讲两种首部,端口就是首部最前面的两个字段。

一、端口属于协议

端口号写在 TCP 或 UDP 的首部里,所以它天然是「属于某个协议」的。 内核里的实现也是两张表:一张记 TCP 端口,一张记 UDP 端口,互不相干。 这就解释了一个常见困惑:「53 端口被占了」为什么还能再起一个 DNS 服务? 因为被占的是「UDP 53」,而新起的是「TCP 53」。 在系统看来,这是两个完全不同的位置。 所以完整的说法永远是「协议加端口」,只说「端口 53」是不严谨的。 这也是 6.2.3 讲「五元组」时必须带上协议号的原因。

二、为什么设计成两张表

因为两个协议的行为完全不同:TCP 有连接,端口要维护「监听、已连接、关闭中」这些状态; UDP 没有连接,端口只是一个「收件箱」。 把它们塞进一张表,等于给 UDP 加一堆用不上的字段。 另一个理由是「可以同时提供两种服务」: DNS 就是标准例子——小查询走 UDP(快、省),大响应和区域传送走 TCP(能传大块)。 两种都在 53,但用户只记一个号码。 这个设计让「用户少记一个号码」和「协议各司其职」同时成立。

三、几个同名不同协议的例子

端口 TCP 上的用途 UDP 上的用途
53 DNS 的大响应、区域传送 DNS 的普通查询
123 基本不用 NTP 时间同步
161 基本不用 SNMP 网管
443 HTTPS QUIC(HTTP/3)

第四行是「新趋势」:QUIC 把「可靠传输」搬到了 UDP 上, 于是 443 这个号码在两种协议上都在用,而且都是网页。 所以「443 就是 HTTPS」这句话依然成立,只是「底下跑的协议」可能是 TCP 也可能是 UDP。 第二、三行说明另一件事:有些服务「只在 UDP 上存在」, 所以「用 TCP 去连 123 端口」会一直连不上——不是服务坏了,是「那个协议上根本没有服务」。

四、动手 Demo:同一个端口号,TCP 和 UDP 是两个门

试着在 53 端口上同时绑 TCP 和 UDP。 两次都成功,就是最直接的证明。

import socket

for typ, name in ((socket.SOCK_STREAM, 'TCP'), (socket.SOCK_DGRAM, 'UDP')):
    s = socket.socket(socket.AF_INET, typ)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', 53))
    print(name, 'ok')
    s.close()

实跑输出:

--------------------------------------------------------------------
同一个端口号,TCP 和 UDP 是两个门
--------------------------------------------------------------------
**端口号只是一个 16 位数字,它属于「某个协议」而不是「某台主机」。**
**所以 53 号端口可以同时被 TCP 和 UDP 占着。**

  协议     地址端口         结果
  -------------------------------
  TCP      127.0.0.1:53     成功
  UDP      127.0.0.1:53     成功

**两行都成功,这不是巧合**:**内核里 TCP 和 UDP 是两张独立的端口表**,
**同一个数字在两张表里各占一个位置。**
**所以 DNS 能「TCP 53 和 UDP 53 同时开」**——
**它确实两个都在用:小查询走 UDP,大响应或区域传送走 TCP。**

**反过来说,同一张表里同一个端口只能被一个程序占**:
**所以「端口被占用」这个错误只会在同一个协议内出现。**
**排查时可以先用 netstat 看「是 TCP 占了还是 UDP 占了」。**

两行都成功,这不是巧合:内核里 TCP 和 UDP 是两张独立的端口表, 同一个数字在两张表里各占一个位置。 反过来说,同一张表里同一个端口只能被一个程序占: 所以「端口被占用」这个错误只会在同一个协议内出现。 排查时先用 netstat 看「是 TCP 占了还是 UDP 占了」,比盲目重启有效得多。 这里用了 SO_REUSEADDR,但即使不开,两次 bind 也会成功:因为它们是两个不同的套接字类型。 这个选项影响的是「同一个协议内的重复绑定」,和跨协议无关。

30 秒实验

  1. 想一下:为什么「端口 53」这个说法不严谨?
  2. 想一下:用 TCP 连 123 端口为什么连不上?
  3. 想一下:QUIC 用了 443,那「443 是 HTTPS」这句话还成立吗?

常见误区

  1. 以为「端口号是全局唯一的资源」。 是按协议分表的,TCP 53 和 UDP 53 互不影响。
  2. 以为「服务在 53 上,TCP UDP 都能连」。 看它开在哪个协议上,NTP 只有 UDP。
  3. 以为「端口占用只能重启解决」。 先分清是哪个协议占的,再决定杀进程还是换端口。
自测三问(点开对答案) 1. 端口号属于「主机」还是「协议」? —— **属于协议**:TCP 和 UDP 各有一张端口表,互不冲突。 2. 为什么 DNS 能同时用 TCP 53 和 UDP 53? —— **两张独立的表**,同一数字各占一个位置,所以可以同时开。 3. NTP 的 123 端口能用 TCP 连吗? —— **不能**,那个端口只开在 UDP 上,TCP 上没有服务。

下一节专门讲 UDP:它到底简化掉了什么。

相关笔记