TCP 端口号和 UDP 端口号的区别:同一个端口能否同时使用
TCP 端口号和 UDP 端口号是两套互不相干的编号空间,各有 0–65535。系统先按 IP 首部的协议号分出 TCP 和 UDP,再到各自的端口表里找程序,所以 TCP 53 和 UDP 53 可以同时被占用、互不冲突。也正因为是两套编号,放行端口时必须写明协议,只开了 TCP 53 的 DNS 服务器,绝大多数查询照样进不来。
TCP 端口号和 UDP 端口号的区别,一句话说完:它们是两套互相独立的编号,各自从 0 到 65535。一个包到达服务器,内核先看 IP 首部里的协议号,6 交给 TCP,17 交给 UDP,然后才在对应协议的端口表里查找是哪个程序在等这个包。所以 TCP 的 53 号和 UDP 的 53 号是两个不同的端口,可以同时被两个程序占用,也可以由同一个程序分别占用。DNS 就是这样用的:UDP 53 处理普通查询,TCP 53 处理大应答和区域传送。反过来,这也意味着防火墙、安全组放行时必须写明协议,查监听端口时要用 ss -tulnp 把两类都列出来。
TCP 端口和 UDP 端口为什么不冲突
端口号写在传输层首部里,而决定用哪个传输层协议解析的,是更外层 IP 首部里的协议号。内核收包的顺序是:
| 步骤 | 看哪个字段 | 例子 |
|---|---|---|
| 1 | IP 首部的目的地址:是不是发给本机的 | 203.0.113.53 |
| 2 | IP 首部的协议号(8 位):交给哪个协议模块 | 6 是 TCP,17 是 UDP,132 是 SCTP |
| 3 | 传输层首部的目的端口(16 位):在该协议的端口表里查找 | 53 |
| 4 | 找到对应的套接字,把数据交给持有它的程序 | named、unbound 等 DNS 服务 |
第 2 步已经把 TCP 和 UDP 分开了,第 3 步只在各自的表里查,两张表互不相干。可以把它理解为两栋楼里的门牌号:A 栋 53 号和 B 栋 53 号是两户人家。由此可以推出几条常被问到的结论:
- TCP 和 UDP 端口可以相同。同一台机器上 TCP 53 和 UDP 53 同时监听,不会报冲突。
- “端口被占用”只发生在同一个协议内。TCP 8080 被占了,UDP 8080 照样能用;
Address already in use报的都是同一协议内的冲突。 - 两套编号的数量互不影响。TCP 用掉多少端口,与 UDP 还剩多少无关,各自都是 65536 个。
- 测试方法不能混用。telnet、
nc -z测的是 TCP 握手,测通了只能说明 TCP 端口通,不能说明同号的 UDP 端口也通。
动手验证:同一个端口号同时监听 TCP 和 UDP
在一台 Linux 上开三个终端,用 nc 分别在 TCP 和 UDP 的 9000 端口上监听(以 Debian、Ubuntu 的 netcat-openbsd 为例;RHEL 系装的是 ncat,把 nc 换成 ncat,参数相同):
# 终端 1:TCP 9000
nc -lk 9000
# 终端 2:UDP 9000
nc -lu 9000
# 终端 3:查看 9000 端口上的监听
ss -tulnp 'sport = :9000'
终端 3 会看到两行,协议列不同,端口相同(示例输出):
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 0.0.0.0:9000 0.0.0.0:* users:(("nc",pid=4122,fd=3))
tcp LISTEN 0 1 0.0.0.0:9000 0.0.0.0:* users:(("nc",pid=4107,fd=3))
这时让第二个程序再去绑定 TCP 9000,就会失败。用 Python 试最直观:python3 -c 'import socket; socket.socket().bind(("0.0.0.0", 9000))' 会报 OSError: [Errno 98] Address already in use,而 UDP 9000 上的 nc 丝毫不受影响。没装 nc 时,同样用 Python 一行就能验证“一个程序同时占用两个同号端口”:
python3 -c 'import socket,time; t=socket.socket(); t.bind(("0.0.0.0",9000)); t.listen(); u=socket.socket(type=socket.SOCK_DGRAM); u.bind(("0.0.0.0",9000)); print("TCP 和 UDP 的 9000 都已绑定"); time.sleep(60)'
53 端口是 TCP 还是 UDP:DNS 两个都用
DNS 是“同一个端口号、两种协议都要用”的典型,各自分工明确:
| 场景 | 协议 | 说明 |
|---|---|---|
| 普通查询,应答较小 | UDP 53 | 一问一答,不用握手,绝大多数查询走这里 |
| 应答超过 UDP 能承载的大小 | 先 UDP,再改 TCP 53 | 服务器回一个置上 TC(截断)标志的应答,客户端看到后用 TCP 重新查询 |
| 区域传送(AXFR;IXFR 通常也走 TCP) | TCP 53 | 主从 DNS 服务器之间同步整个区的记录 |
| DNS over TLS | TCP 853 | 加密 DNS,端口不同 |
| DNS over QUIC | UDP 853 | 同样是 853,换成 UDP |
UDP 能承载多大的应答,经历过几次变化:最早的 RFC 1035 把 UDP 上的 DNS 报文限制在 512 字节;EDNS(0)(RFC 6891)允许客户端在查询里声明自己能接收更大的 UDP 报文;2020 年的 DNS Flag Day 建议把这个值设为 1232 字节,以避开 IP 分片。超过声明大小的应答,就要截断后改走 TCP。RFC 7766 进一步要求所有通用的 DNS 实现都必须同时支持 UDP 和 TCP,TCP 不再是可有可无的备用通道。
用 dig 可以亲眼看到切换过程。关闭 EDNS、要求 dig 遇到截断不要自动重试,应答一旦超过 512 字节,返回的头部标志里就会出现 tc(把 example.com 换成一个 TXT 记录较多、应答超过 512 字节的域名):
# 只用 UDP、不带 EDNS,并忽略截断:大应答会显示 flags: qr tc ...
dig @203.0.113.53 example.com TXT +noedns +ignore
# 去掉 +ignore,dig 会提示 ;; Truncated, retrying in TCP mode. 然后改用 TCP 重查
dig @203.0.113.53 example.com TXT +noedns
# 直接用 TCP 查询,验证 TCP 53 是否放行
dig @203.0.113.53 example.com +tcp
这套机制对放行规则的要求很直接。自己运行权威 DNS 的服务器,入方向要同时放行 UDP 53 和 TCP 53:区里签了 DNSSEC、某个名字下挂着很多 A 记录或很长的 TXT 记录时,应答很容易超过 1232 字节,只开 UDP 的话,这部分查询会在截断之后失败,而且只有特定域名、特定解析器出问题,很难一眼看出原因。主从同步也走 TCP:从服务器主动连接主服务器的 TCP 53 拉取区数据,主服务器只放行 UDP 的话,从服务器永远同步不到。
同一个号码,在 TCP 和 UDP 上不一定是同一个服务
IANA 给大多数知名服务分配端口时,会把 TCP 和 UDP 的同号一起登记给它,哪怕实际只用其中一种;但也有少数号码,TCP 和 UDP 登记的是完全不同的服务。下表按“同号端口在两种协议上分别是什么”整理,放行时据此决定开哪一边:
| 端口 | TCP 上 | UDP 上 | 放行建议 |
|---|---|---|---|
| 22 | SSH | IANA 也登记给 SSH,实际不用 | 只放 TCP |
| 53 | DNS 大应答、区域传送 | DNS 普通查询 | 权威 DNS 两边都放 |
| 80 | HTTP | 登记了,实际没有服务使用 | 只放 TCP |
| 88 | Kerberos,报文较大时 | Kerberos | 域控两边都放 |
| 123 | 登记了,实际不用 | NTP | 只放 UDP |
| 389 | LDAP | CLDAP,Active Directory 定位域控时使用 | 域控两边都放 |
| 443 | HTTPS(HTTP/1.1、HTTP/2) | HTTP/3(QUIC) | 启用了 HTTP/3 才放 UDP |
| 512 | exec(rexec) | biff(comsat) | 两个不同的老服务,都不应对外开放 |
| 513 | login(rlogin) | who(rwhod) | 同上 |
| 514 | shell(rsh) | syslog | 收日志一般只放 UDP;rsyslog 走 TCP 时习惯上也用 514 |
| 520 | efs | RIP 路由协议 | 按实际用途放行 |
| 3389 | RDP 会话 | RDP 8.0 起的 UDP 传输 | 对外开 RDP 时两边都放,网络较差时更流畅 |
| 5060 | SIP | SIP | 两边都放;SIP over TLS 用 TCP 5061 |
512–514、520 这几个号码说明了一个事实:只说“开 514 端口”,可能被理解成开 rsh,也可能是收 syslog,工单里必须写清协议。本机的服务名数据库里能直接查到这种差别:
getent services 514/tcp # 输出 shell 514/tcp cmd
getent services 514/udp # 输出 syslog 514/udp
防火墙、安全组放行:协议必须写清楚
几乎所有防火墙规则都把“协议 + 端口”作为一个整体来匹配。以一台权威 DNS 服务器 203.0.113.53 为例,各种工具的写法如下。
iptables 和 nftables:
# iptables:--dport 必须跟在 -p tcp 或 -p udp 后面,不写协议会报 unknown option "--dport"
iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables -A INPUT -p tcp --dport 53 -j ACCEPT
# nftables:一条规则同时匹配两种协议
nft add rule inet filter input meta l4proto { tcp, udp } th dport 53 accept
firewalld 和 ufw:
# firewalld:两个端口分别写协议;预定义的 dns 服务已经包含 53/tcp 和 53/udp
firewall-cmd --permanent --add-port=53/tcp --add-port=53/udp && firewall-cmd --reload
firewall-cmd --permanent --add-service=dns && firewall-cmd --reload
# ufw:写协议只放一种,不写协议则 TCP、UDP 一起放行
ufw allow 53/udp
ufw allow 53
ufw 的“不写协议就两种都放”很方便,也容易放多了:ufw allow 3306 会顺带放行 UDP 3306,虽然 MySQL 并不监听它,但规则比实际需要宽。能写协议的时候就写上。
Windows 防火墙每条规则只能选一种协议,TCP、UDP 各建一条:
New-NetFirewallRule -DisplayName "DNS UDP 53" -Direction Inbound -Protocol UDP -LocalPort 53 -Action Allow
New-NetFirewallRule -DisplayName "DNS TCP 53" -Direction Inbound -Protocol TCP -LocalPort 53 -Action Allow
托管机房没有安全组,靠边界交换机或路由器的 ACL 放行时,同样是两条规则。华为和思科的写法:
# 华为高级 ACL
acl number 3001
rule 5 permit udp destination 203.0.113.53 0 destination-port eq 53
rule 10 permit tcp destination 203.0.113.53 0 destination-port eq 53
# 思科扩展 ACL
ip access-list extended DNS-IN
permit udp any host 203.0.113.53 eq 53
permit tcp any host 203.0.113.53 eq 53
云平台的安全组也是同样的逻辑:规则里的协议一栏要么选 TCP,要么选 UDP,同一个端口两种都要时就建两条。不要图省事选“全部协议”,那会把这个来源对这台服务器的所有端口一起打开。
用 ss -tulnp 同时查看 TCP 和 UDP 监听端口
ss -tulnp 是排查端口问题的第一条命令,五个参数各管一件事:-t 列 TCP,-u 列 UDP,-l 只看监听中的,-n 端口显示数字不转换成服务名,-p 显示占用端口的进程(需要 root)。一台跑着 DNS 服务的机器,输出类似这样(示例):
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.1:323 0.0.0.0:* users:(("chronyd",pid=702,fd=5))
udp UNCONN 0 0 203.0.113.53:53 0.0.0.0:* users:(("named",pid=1180,fd=512))
tcp LISTEN 0 10 203.0.113.53:53 0.0.0.0:* users:(("named",pid=1180,fd=514))
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=905,fd=3))
读法要点:
- 第一列 Netid 就是协议。53 端口出现了两次,一次 udp、一次 tcp,同属 named 这一个进程,正是“同号端口两种协议都用”。
- UDP 的状态是 UNCONN,不是 LISTEN。UDP 没有连接,只要绑定了端口就算在“监听”。不加
-l、用ss -uan查看时,偶尔会看到状态为 ESTAB 的 UDP 行,那是程序对 UDP 套接字调用了 connect,固定了对端地址,并不代表建立了连接。 - 地址决定谁能访问。chronyd 的 UDP 323 只绑在 127.0.0.1,只给本机的 chronyc 用,防火墙放不放行都不影响;named 绑在 203.0.113.53,外部能否访问取决于防火墙。
几个常用的变体:
ss -ulnp # 只看 UDP
ss -tlnp # 只看 TCP
ss -tulnp 'sport = :53' # 只看 53 端口,两种协议都列出
lsof -nP -i :53 # 用 lsof 查,同样会列出 TCP 和 UDP
Windows 上 TCP 和 UDP 要分开查:
Get-NetTCPConnection -LocalPort 53 -State Listen
Get-NetUDPEndpoint -LocalPort 53
netstat -ano -p UDP | findstr :53
netstat -ano 其实同时列出 TCP 和 UDP,但 UDP 端点没有状态,习惯用 findstr LISTENING 过滤的话,UDP 端口会被全部漏掉。排查“端口开了却连不上”时,先用这些命令确认服务实际监听的是哪种协议、哪个地址,再去对照防火墙规则,能省掉大半时间。
在机房里怎么落地
托管和独立服务器多数没有云平台那样的安全组,端口放行落在两处:服务器自己的防火墙,以及机房边界设备上的 ACL。边界 ACL 按协议写规则,改动频繁时最怕“谁加了一条 permit ip any”说不清。Toplink DCIM 的交换机管理支持在浏览器里打开交换机的 SSH 终端,会话全程录像并记录命令级日志,每条 ACL 规则的增删都能追溯到人和时间。服务器侧防火墙规则写错、把 SSH 的 TCP 22 也挡在外面时,客户可以在客户自助端打开 VNC 控制台登录系统,自己改回规则,不必提工单等工程师进机房。
常见问题
HTTPS 的 TCP 443 和 HTTP/3 的 UDP 443 会冲突吗?
不会,它们是两个端口。Nginx 1.25 起支持 HTTP/3,可以在同一个 server 块里同时写 listen 443 ssl; 和 listen 443 quic reuseport;,前者监听 TCP 443,后者监听 UDP 443。响应头里还要加 Alt-Svc 告诉浏览器可以用 HTTP/3,防火墙和安全组也要另外放行 UDP 443,否则浏览器会退回 TCP 上的 HTTP/2 或 HTTP/1.1,网站照样能打开,只是没用上 HTTP/3。
IPv4 和 IPv6 的同一个端口算一个吗?
要看程序怎么监听。Linux 默认 net.ipv6.bindv6only 为 0,监听 [::]:80 的 IPv6 套接字会同时接收 IPv4 连接,此时再单独监听 0.0.0.0:80 会报地址已被占用;程序给套接字设置了 IPV6_V6ONLY 后,两者才能分别监听。Nginx 的 listen [::]:80 默认就是只收 IPv6,所以要和 listen 80 一起写。
除了 TCP 和 UDP,还有别的协议有端口吗?
有。SCTP(IP 协议号 132)也有自己独立的 16 位端口,常见于电信信令网,IDC 业务很少遇到;放行时要在防火墙里选 SCTP 协议。ICMP 没有端口,ping 走的就是 ICMP,所以 ping 得通和端口通不通没有关系。