字節(jié)一面:TCP和UDP可以使用同一個端口號嗎?
首先說答案:可以。怎么理解呢?
我想這個問題要從計算機網(wǎng)絡(luò)通信談起,學(xué)過計算機網(wǎng)絡(luò)的同學(xué),可能都還記得7層或者4層網(wǎng)絡(luò)模型,TCP/UDP屬于其中的傳輸層協(xié)議,在傳輸層之下是網(wǎng)絡(luò)層,網(wǎng)絡(luò)層主要通過IP協(xié)議來進行通信,這也是我們?nèi)粘3绦蜷_發(fā)中能夠接觸到的最底層了,再往下的數(shù)據(jù)鏈路層和物理層就不是我們這些普通程序員需要關(guān)心的了。
圖片
IP
我們先具體看下網(wǎng)絡(luò)層。在IP網(wǎng)路層,發(fā)送者向接收者傳輸數(shù)據(jù)的時候,首先需要知道接收者的IP地址,IP地址可以在網(wǎng)絡(luò)中唯一標(biāo)識一臺計算機,然后數(shù)據(jù)就可以根據(jù)IP協(xié)議抵達接收者所在的計算機,但是接收者所在的計算機上運行了幾十個程序,計算機應(yīng)該把這個數(shù)據(jù)交給哪個程序呢?
端口號
這就像快遞員到達了一棟大樓,下一步它怎么把快遞送到對應(yīng)的用戶手中呢?聰明的你一定想到了,那就是門牌號。
在計算機中,端口號就是門牌號。計算機的操作系統(tǒng)可以為不同的程序綁定不同的端口號,這樣發(fā)送者發(fā)送數(shù)據(jù)時不僅要設(shè)置接收者的IP,還要加上接收者的端口號,如此接收者所在的計算機就能把數(shù)據(jù)轉(zhuǎn)發(fā)給正確的程序了。
TCP/UDP
那么TCP和UDP能不能使用同一個端口號呢?其實在查找端口號之前還有一個傳輸層協(xié)議的處理過程,操作系統(tǒng)收到數(shù)據(jù)后,會先查看數(shù)據(jù)包使用的是TCP協(xié)議還是UDP協(xié)議,然后再根據(jù)協(xié)議進行不同的解析處理,提取到數(shù)據(jù)后,再轉(zhuǎn)發(fā)到擁有對應(yīng)端口的程序。
所以TCP和UDP是可以使用相同的端口號的,這在現(xiàn)實中也是常見的。比如 DNS(域名系統(tǒng))可能需要同時支持 TCP 和 UDP 查詢,這兩種查詢就都可以通過53這個標(biāo)準(zhǔn)端口來進行接收和響應(yīng)。
但是在同一個傳輸協(xié)議下,端口號就不能相同了。如果相同,操作系統(tǒng)的協(xié)議棧就不知道該把這個數(shù)據(jù)包轉(zhuǎn)給哪個程序了,這種設(shè)計會增加很多麻煩。
有的同學(xué)可能會觀察到一個現(xiàn)象,那就是同一個計算機上的多個網(wǎng)站可以共享80或者443端口,這其實是應(yīng)用層的能力,這些網(wǎng)站都寄宿在同一個Web服務(wù)器程序上,這個Web服務(wù)器程序綁定了80端口,Web服務(wù)器收到數(shù)據(jù)后再根據(jù)HTTP協(xié)議中的主機頭(可以理解成域名)轉(zhuǎn)發(fā)給不同的網(wǎng)站程序。
還有,如果你的電腦上有多個IP,那就更沒有問題了。不同的IP代表不同的網(wǎng)絡(luò)接口,即使都使用TCP協(xié)議,只要IP不同,端口號一樣也完全不會沖突。
“IP+傳輸層協(xié)議+端口號”就是我們常說的套接字,它能確保數(shù)據(jù)從一個網(wǎng)絡(luò)程序傳遞到另一個網(wǎng)絡(luò)程序。大家如果直接使用TCP和UDP編程,就需要手動為套接字設(shè)置這幾個參數(shù)。
示例
口說無憑,再給大家寫個demo,使用go語言,簡單易懂:
下邊的程序會啟動一個TCP服務(wù)器和一個UDP服務(wù)器,它們綁定相同的IP和端口號。這里為了方便測試,使用了127.0.0.1這個本機IP,你也可以換成局域網(wǎng)或者公網(wǎng)IP。
package main
import (
"fmt"
"net"
"os"
)
func main() {
// 定義監(jiān)聽的端口
port := "127.0.0.1:12345"
// 啟動TCP服務(wù)器
go startTCPServer(port)
// 啟動UDP服務(wù)器
startUDPServer(port)
}
func startTCPServer(port string) {
// 通過TCP協(xié)議監(jiān)聽端口
l, err := net.Listen("tcp", port)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer l.Close()
fmt.Println("TCP Server Listening on " + port)
// 持續(xù)接收TCP數(shù)據(jù)
for {
conn, err := l.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println("Received TCP connection")
conn.Close()
}
}
func startUDPServer(port string) {
// 通過UDP協(xié)議監(jiān)聽端口
addr, err := net.ResolveUDPAddr("udp", port)
if err != nil {
fmt.Println("Error resolving: ", err.Error())
os.Exit(1)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("Error listening: ", err.Error())
os.Exit(1)
}
defer conn.Close()
fmt.Println("UDP Server Listening on " + port)
buffer := make([]byte, 1024)
// 持續(xù)接收UDP數(shù)據(jù)
for {
n, _, err := conn.ReadFromUDP(buffer)
if err != nil {
fmt.Println("Error reading: ", err.Error())
continue
}
fmt.Printf("Received UDP packet: %s\n", string(buffer[:n]))
}
}
然后再創(chuàng)建兩個客戶端,一個是TCP客戶端:
package main
import (
"fmt"
"net"
"os"
)
func main() {
// 連接到服務(wù)器
conn, err := net.Dial("tcp", "localhost:12345")
if err != nil {
fmt.Println("Error connecting:", err.Error())
os.Exit(1)
}
defer conn.Close()
// 發(fā)送數(shù)據(jù)
_, err = conn.Write([]byte("Hello TCP Server!"))
if err != nil {
fmt.Println("Error sending data:", err.Error())
return
}
fmt.Println("Message sent to TCP server")
}
另一個是UDP客戶端:
package main
import (
"fmt"
"net"
"os"
)
func main() {
ServerAddr, err := net.ResolveUDPAddr("udp", "localhost:12345")
if err != nil {
fmt.Println("Error resolving: ", err.Error())
os.Exit(1)
}
conn, err := net.DialUDP("udp", nil, ServerAddr)
if err != nil {
fmt.Println("Error dialing: ", err.Error())
os.Exit(1)
}
defer conn.Close()
// 發(fā)送數(shù)據(jù)
_, err = conn.Write([]byte("Hello UDP Server!"))
if err != nil {
fmt.Println("Error sending data:", err.Error())
return
}
fmt.Println("Message sent to UDP server")
}
我們可以看到,客戶端發(fā)起請求的時候都使用了 localhost:12345 這個目標(biāo)地址,其中的localhost 實際上是個域名,它會被本地計算機解析為 127.0.0.1。這塊不清楚的可以看我之前寫的這篇:
實際運行效果如下:
圖片
最后總結(jié)下:在網(wǎng)絡(luò)通信中,同一臺計算機中,TCP和UDP協(xié)議可以使用相同的端口號。每個網(wǎng)絡(luò)進程中的套接字地址都是唯一的,由三元組(IP地址,傳輸層協(xié)議,端口號)標(biāo)識。操作系統(tǒng)會根據(jù)數(shù)據(jù)包中的傳輸層協(xié)議(TCP或UDP)以及端口號,將接收到的數(shù)據(jù)正確地交付給相應(yīng)的應(yīng)用程序。