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 得通和連接埠通不通沒有關係。