大家好,我是小林。
之前有讀者在字節(jié)面試的時候,被問到:TCP 和 UDP 可以同時監(jiān)聽相同的端口嗎?
關(guān)于端口的知識點(diǎn),還是挺多可以講的,比如還可以牽扯到這幾個問題:
(資料圖)
所以,這次就跟大家盤一盤這些問題。
TCP 和 UDP 可以同時綁定相同的端口嗎?其實(shí)我感覺這個問題「TCP 和 UDP 可以同時監(jiān)聽相同的端口嗎?」表述有問題,這個問題應(yīng)該表述成「TCP 和 UDP 可以同時綁定相同的端口嗎?」
因?yàn)椤副O(jiān)聽」這個動作是在 TCP 服務(wù)端網(wǎng)絡(luò)編程中才具有的,而 UDP 服務(wù)端網(wǎng)絡(luò)編程中是沒有「監(jiān)聽」這個動作的。
TCP 和 UDP 服務(wù)端網(wǎng)絡(luò)相似的一個地方,就是會調(diào)用 bind 綁定端口。
給大家貼一下 TCP 和 UDP 網(wǎng)絡(luò)編程的區(qū)別就知道了。
TCP 網(wǎng)絡(luò)編程如下,服務(wù)端執(zhí)行 listen() 系統(tǒng)調(diào)用就是監(jiān)聽端口的動作。
TCP 網(wǎng)絡(luò)編程
UDP 網(wǎng)絡(luò)編程如下,服務(wù)端是沒有監(jiān)聽這個動作的,只有執(zhí)行 bind() 系統(tǒng)調(diào)用來綁定端口的動作。
UDP 網(wǎng)絡(luò)編程
TCP 和 UDP 可以同時綁定相同的端口嗎?答案:可以的。
在數(shù)據(jù)鏈路層中,通過 MAC 地址來尋找局域網(wǎng)中的主機(jī)。在網(wǎng)際層中,通過 IP 地址來尋找網(wǎng)絡(luò)中互連的主機(jī)或路由器。在傳輸層中,需要通過端口進(jìn)行尋址,來識別同一計算機(jī)中同時通信的不同應(yīng)用程序。
所以,傳輸層的「端口號」的作用,是為了區(qū)分同一個主機(jī)上不同應(yīng)用程序的數(shù)據(jù)包。
傳輸層有兩個傳輸協(xié)議分別是 TCP 和 UDP,在內(nèi)核中是兩個完全獨(dú)立的軟件模塊。
當(dāng)主機(jī)收到數(shù)據(jù)包后,可以在 IP 包頭的「協(xié)議號」字段知道該數(shù)據(jù)包是 TCP/UDP,所以可以根據(jù)這個信息確定送給哪個模塊(TCP/UDP)處理,送給 TCP/UDP 模塊的報文根據(jù)「端口號」確定送給哪個應(yīng)用程序處理。
因此, TCP/UDP 各自的端口號也相互獨(dú)立,如 TCP 有一個 80 號端口,UDP 也可以有一個 80 號端口,二者并不沖突。
驗(yàn)證結(jié)果我簡單寫了 TCP 和 UDP 服務(wù)端的程序,它們都綁定同一個端口號 8888。
運(yùn)行這兩個程序后,通過 netstat 命令可以看到,TCP 和 UDP 是可以同時綁定同一個端口號的。
多個 TCP 服務(wù)進(jìn)程可以綁定同一個端口嗎?還是以前面的 TCP 服務(wù)端程序作為例子,啟動兩個同時綁定同一個端口的 TCP 服務(wù)進(jìn)程。
運(yùn)行第一個 TCP 服務(wù)進(jìn)程之后,netstat 命令可以查看,8888 端口已經(jīng)被一個 TCP 服務(wù)進(jìn)程綁定并監(jiān)聽了,如下圖:
接著,運(yùn)行第二個 TCP 服務(wù)進(jìn)程的時候,就報錯了“Address already in use”,如下圖:
我上面的測試案例是兩個 TCP 服務(wù)進(jìn)程同時綁定地址和端口是:0.0.0.0 地址和8888端口,所以才出現(xiàn)的錯誤。
如果兩個 TCP 服務(wù)進(jìn)程綁定的 IP 地址不同,而端口相同的話,也是可以綁定成功的,如下圖:
所以,默認(rèn)情況下,針對「多個 TCP 服務(wù)進(jìn)程可以綁定同一個端口嗎?」這個問題的答案是:如果兩個 TCP 服務(wù)進(jìn)程同時綁定的 IP 地址和端口都相同,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。
注意,如果 TCP 服務(wù)進(jìn)程 A 綁定的地址是 0.0.0.0 和端口 8888,而如果 TCP 服務(wù)進(jìn)程 B 綁定的地址是 192.168.1.100 地址(或者其他地址)和端口 8888,那么執(zhí)行 bind() 時候也會出錯。
這是因?yàn)?0.0.0.0 地址比較特殊,代表任意地址,意味著綁定了 0.0.0.0 地址,相當(dāng)于把主機(jī)上的所有 IP 地址都綁定了。
重啟 TCP 服務(wù)進(jìn)程時,為什么會有“Address in use”的報錯信息?TCP 服務(wù)進(jìn)程需要綁定一個 IP 地址和一個端口,然后就監(jiān)聽在這個地址和端口上,等待客戶端連接的到來。
然后在實(shí)踐中,我們可能會經(jīng)常碰到一個問題,當(dāng) TCP 服務(wù)進(jìn)程重啟之后,總是碰到“Address in use”的報錯信息,TCP 服務(wù)進(jìn)程不能很快地重啟,而是要過一會才能重啟成功。
這是為什么呢?
當(dāng)我們重啟 TCP 服務(wù)進(jìn)程的時候,意味著通過服務(wù)器端發(fā)起了關(guān)閉連接操作,于是就會經(jīng)過四次揮手,而對于主動關(guān)閉方,會在 TIME_WAIT 這個狀態(tài)里停留一段時間,這個時間大約為 2MSL。
當(dāng) TCP 服務(wù)進(jìn)程重啟時,服務(wù)端會出現(xiàn) TIME_WAIT 狀態(tài)的連接,TIME_WAIT 狀態(tài)的連接使用的 IP+PORT 仍然被認(rèn)為是一個有效的 IP+PORT 組合,相同機(jī)器上不能夠在該 IP+PORT 組合上進(jìn)行綁定,那么執(zhí)行 bind() 函數(shù)的時候,就會返回了 Address already in use 的錯誤。
而等 TIME_WAIT 狀態(tài)的連接結(jié)束后,重啟 TCP 服務(wù)進(jìn)程就能成功。
重啟 TCP 服務(wù)進(jìn)程時,如何避免“Address in use”的報錯信息?我們可以在調(diào)用 bind 前,對 socket 設(shè)置 SO_REUSEADDR 屬性,可以解決這個問題。
int on = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
因?yàn)镾O_REUSEADDR作用是:如果當(dāng)前啟動進(jìn)程綁定的 IP+PORT 與處于TIME_WAIT 狀態(tài)的連接占用的 IP+PORT 存在沖突,但是新啟動的進(jìn)程使用了 SO_REUSEADDR 選項(xiàng),那么該進(jìn)程就可以綁定成功。
舉個例子,服務(wù)端有個監(jiān)聽 0.0.0.0 地址和 8888 端口的 TCP 服務(wù)進(jìn)程。?
有個客戶端(IP地址:192.168.1.100)已經(jīng)和服務(wù)端(IP 地址:172.19.11.200)建立了 TCP 連接,那么在 TCP 服務(wù)進(jìn)程重啟時,服務(wù)端會與客戶端經(jīng)歷四次揮手,服務(wù)端的 TCP 連接會短暫處于 TIME_WAIT 狀態(tài):
客戶端地址:端口 服務(wù)端地址:端口 TCP 連接狀態(tài)192.168.1.100:37272 172.19.11.200:8888 TIME_WAIT
如果 TCP 服務(wù)進(jìn)程沒有對 socket 設(shè)置 SO_REUSEADDR 屬性,那么在重啟時,由于存在一個和綁定 IP+PORT 一樣的TIME_WAIT 狀態(tài)的連接,那么在執(zhí)行 bind() 函數(shù)的時候,就會返回了 Address already in use 的錯誤。
如果 TCP 服務(wù)進(jìn)程對 socket 設(shè)置 SO_REUSEADDR 屬性了,那么在重啟時,即使存在一個和綁定 IP+PORT一樣的TIME_WAIT 狀態(tài)的連接,依然可以正常綁定成功,因此可以正常重啟成功。
因此,在所有 TCP 服務(wù)器程序中,調(diào)用 bind 之前最好對 socket 設(shè)置 SO_REUSEADDR 屬性,這不會產(chǎn)生危害,相反,它會幫助我們在很快時間內(nèi)重啟服務(wù)端程序。?
前面我提到過這個問題:如果 TCP 服務(wù)進(jìn)程 A 綁定的地址是 0.0.0.0 和端口 8888,而如果 TCP 服務(wù)進(jìn)程 B 綁定的地址是 192.168.1.100 地址(或者其他地址)和端口 8888,那么執(zhí)行 bind() 時候也會出錯。
這個問題也可以由SO_REUSEADDR解決,因?yàn)樗牧硗庖粋€作用是:綁定的 IP地址 + 端口時,只要 IP 地址不是正好(exactly)相同,那么允許綁定。?
比如,0.0.0.0:8888和192.168.1.100:8888,雖然邏輯意義上前者包含了后者,但是 0.0.0.0 泛指所有本地 IP,而 192.168.1.100 特指某一IP,兩者并不是完全相同,所以在對 socket 設(shè)置SO_REUSEADDR 屬性后,那么執(zhí)行 bind() 時候就會綁定成功。
客戶端的端口可以重復(fù)使用嗎?客戶端在執(zhí)行 connect 函數(shù)的時候,會在內(nèi)核里隨機(jī)選擇一個端口,然后向服務(wù)端發(fā)起 SYN 報文,然后與服務(wù)端進(jìn)行三次握手。
所以,客戶端的端口選擇的發(fā)生在 connect 函數(shù),內(nèi)核在選擇端口的時候,會從net.ipv4.ip_local_port_range這個內(nèi)核參數(shù)指定的范圍來選取一個端口作為客戶端端口。
該參數(shù)的默認(rèn)值是 32768 61000,意味著端口總可用的數(shù)量是 61000 - 32768 = 28232 個。
當(dāng)客戶端與服務(wù)端完成 TCP 連接建立后,我們可以通過 netstat 命令查看 TCP 連接。
$ netstat -napt協(xié)議 源ip地址:端口 目的ip地址:端口 狀態(tài)tcp 192.168.110.182.64992 117.147.199.51.443 ESTABLISHED
那問題來了,上面客戶端已經(jīng)用了 64992 端口,那么還可以繼續(xù)使用該端口發(fā)起連接嗎?
這個問題,很多同學(xué)都會說不可以繼續(xù)使用該端口了,如果按這個理解的話, 默認(rèn)情況下客戶端可以選擇的端口是 28232 個,那么意味著客戶端只能最多建立 28232 個 TCP 連接,如果真是這樣的話,那么這個客戶端并發(fā)連接也太少了吧,所以這是錯誤理解。
正確的理解是,TCP 連接是由四元組(源IP地址,源端口,目的IP地址,目的端口)唯一確認(rèn)的,那么只要四元組中其中一個元素發(fā)生了變化,那么就表示不同的 TCP 連接的。所以如果客戶端已使用端口 64992 與服務(wù)端 A 建立了連接,那么客戶端要與服務(wù)端 B 建立連接,還是可以使用端口 64992 的,因?yàn)閮?nèi)核是通過四元祖信息來定位一個 TCP 連接的,并不會因?yàn)榭蛻舳说亩丝谔栂嗤鴮?dǎo)致連接沖突的問題。
比如下面這張圖,有 2 個 TCP 連接,左邊是客戶端,右邊是服務(wù)端,客戶端使用了相同的端口 50004 與兩個服務(wù)端建立了 TCP 連接。
仔細(xì)看,上面這兩條 TCP 連接的四元組信息中的「目的 IP 地址」是不同的,一個是 180.101.49.12 ,另外一個是 180.101.49.11。
多個客戶端可以 bind 同一個端口嗎?bind 函數(shù)雖然常用于服務(wù)端網(wǎng)絡(luò)編程中,但是它也是用于客戶端的。
前面我們知道,客戶端是在調(diào)用 connect 函數(shù)的時候,由內(nèi)核隨機(jī)選取一個端口作為連接的端口。
而如果我們想自己指定連接的端口,就可以用 bind 函數(shù)來實(shí)現(xiàn):客戶端先通過 bind 函數(shù)綁定一個端口,然后調(diào)用 connect 函數(shù)就會跳過端口選擇的過程了,轉(zhuǎn)而使用 bind 時確定的端口。
針對這個問題:多個客戶端可以 bind 同一個端口嗎?
要看多個客戶端綁定的IP+PORT 是否都相同,如果都是相同的,那么在執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。
如果一個綁定在 192.168.1.100:6666,一個綁定在192.168.1.200:6666,因?yàn)?IP 不相同,所以執(zhí)行 bind() 的時候,能正常綁定。
所以, 如果多個客戶端同時綁定的 IP 地址和端口都是相同的,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。
一般而言,客戶端不建議使用 bind 函數(shù),應(yīng)該交由 connect 函數(shù)來選擇端口會比較好,因?yàn)榭蛻舳说亩丝谕ǔ6紱]什么意義。
客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會導(dǎo)致端口資源耗盡而無法建立新的連接嗎?針對這個問題要看,客戶端是否都是與同一個服務(wù)器(目標(biāo)地址和目標(biāo)端口一樣)建立連接。
如果客戶端都是與同一個服務(wù)器(目標(biāo)地址和目標(biāo)端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當(dāng)端口資源被耗盡,就無法與這個服務(wù)器再建立連接了。
但是,因?yàn)橹灰蛻舳诉B接的服務(wù)器不同,端口資源可以重復(fù)使用的。
所以,如果客戶端都是與不同的服務(wù)器建立連接,即使客戶端端口資源只有幾萬個, 客戶端發(fā)起百萬級連接也是沒問題的(當(dāng)然這個過程還會受限于其他資源,比如文件描述符、內(nèi)存、CPU 等)。
如何解決客戶端 TCP 連接 TIME_WAIT 過多,導(dǎo)致無法與同一個服務(wù)器建立連接的問題?前面我們提到,如果客戶端都是與同一個服務(wù)器(目標(biāo)地址和目標(biāo)端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當(dāng)端口資源被耗盡,就無法與這個服務(wù)器再建立連接了。
針對這個問題,也是有解決辦法的,那就是打開net.ipv4.tcp_tw_reuse這個內(nèi)核參數(shù)。
因?yàn)殚_啟了這個內(nèi)核參數(shù)后,客戶端調(diào)用 connect 函數(shù)時,如果選擇到的端口,已經(jīng)被相同四元組的連接占用的時候,就會判斷該連接是否處于 TIME_WAIT 狀態(tài),如果該連接處于 TIME_WAIT 狀態(tài)并且 TIME_WAIT 狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用這個連接,然后就可以正常使用該端口了。
舉個例子,假設(shè)客戶端已經(jīng)與服務(wù)器建立了一個 TCP 連接,并且這個狀態(tài)處于 TIME_WAIT 狀態(tài):
客戶端地址:端口 服務(wù)端地址:端口 TCP 連接狀態(tài)192.168.1.100:2222 172.19.11.21:8888 TIME_WAIT
然后客戶端又與該服務(wù)器(172.19.11.21:8888)發(fā)起了連接,在調(diào)用 connect 函數(shù)時,內(nèi)核剛好選擇了 2222 端口,接著發(fā)現(xiàn)已經(jīng)被相同四元組的連接占用了:
如果沒有開啟net.ipv4.tcp_tw_reuse 內(nèi)核參數(shù),那么內(nèi)核就會選擇下一個端口,然后繼續(xù)判斷,直到找到一個沒有被相同四元組的連接使用的端口, 如果端口資源耗盡還是沒找到,那么 connect 函數(shù)就會返回錯誤。如果開啟了 net.ipv4.tcp_tw_reuse 內(nèi)核參數(shù),就會判斷該四元組的連接狀態(tài)是否處于 TIME_WAIT 狀態(tài),如果連接處于 TIME_WAIT 狀態(tài)并且該狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用該連接,于是就可以使用 2222 端口了,這時 connect 就會返回成功。再次提醒一次,開啟了 net.ipv4.tcp_tw_reuse 內(nèi)核參數(shù),是客戶端(連接發(fā)起方) 在調(diào)用 connect() 函數(shù)時才起作用,所以在服務(wù)端開啟這個參數(shù)是沒有效果的。
客戶端端口選擇的流程總結(jié)至此,我們已經(jīng)把客戶端在執(zhí)行 connect 函數(shù)時,內(nèi)核選擇端口的情況大致說了一遍,為了讓大家更明白客戶端端口的選擇過程,我畫了一流程圖。
總結(jié)TCP 和 UDP 可以同時綁定相同的端口嗎?可以的。
TCP 和 UDP 傳輸協(xié)議,在內(nèi)核中是由兩個完全獨(dú)立的軟件模塊實(shí)現(xiàn)的。
當(dāng)主機(jī)收到數(shù)據(jù)包后,可以在 IP 包頭的「協(xié)議號」字段知道該數(shù)據(jù)包是 TCP/UDP,所以可以根據(jù)這個信息確定送給哪個模塊(TCP/UDP)處理,送給 TCP/UDP 模塊的報文根據(jù)「端口號」確定送給哪個應(yīng)用程序處理。
因此, TCP/UDP 各自的端口號也相互獨(dú)立,互不影響。
多個 TCP 服務(wù)進(jìn)程可以同時綁定同一個端口嗎?如果兩個 TCP 服務(wù)進(jìn)程同時綁定的 IP 地址和端口都相同,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。
如果兩個 TCP 服務(wù)進(jìn)程綁定的端口都相同,而 IP 地址不同,那么執(zhí)行 bind()不會出錯。
如何解決服務(wù)端重啟時,報錯“Address already in use”的問題?當(dāng)我們重啟 TCP 服務(wù)進(jìn)程的時候,意味著通過服務(wù)器端發(fā)起了關(guān)閉連接操作,于是就會經(jīng)過四次揮手,而對于主動關(guān)閉方,會在 TIME_WAIT 這個狀態(tài)里停留一段時間,這個時間大約為 2MSL。
當(dāng) TCP 服務(wù)進(jìn)程重啟時,服務(wù)端會出現(xiàn) TIME_WAIT 狀態(tài)的連接,TIME_WAIT 狀態(tài)的連接使用的 IP+PORT 仍然被認(rèn)為是一個有效的 IP+PORT 組合,相同機(jī)器上不能夠在該 IP+PORT 組合上進(jìn)行綁定,那么執(zhí)行 bind() 函數(shù)的時候,就會返回了 Address already in use 的錯誤。
要解決這個問題,我們可以對 socket 設(shè)置 SO_REUSEADDR 屬性。
這樣即使存在一個和綁定 IP+PORT一樣的TIME_WAIT 狀態(tài)的連接,依然可以正常綁定成功,因此可以正常重啟成功。
客戶端的端口可以重復(fù)使用嗎?在客戶端執(zhí)行 connect 函數(shù)的時候,只要客戶端連接的服務(wù)器不是同一個,內(nèi)核允許端口重復(fù)使用。
TCP 連接是由四元組(源IP地址,源端口,目的IP地址,目的端口)唯一確認(rèn)的,那么只要四元組中其中一個元素發(fā)生了變化,那么就表示不同的 TCP 連接的。
所以,如果客戶端已使用端口 64992 與服務(wù)端 A 建立了連接,那么客戶端要與服務(wù)端 B 建立連接,還是可以使用端口 64992 的,因?yàn)閮?nèi)核是通過四元祖信息來定位一個 TCP 連接的,并不會因?yàn)榭蛻舳说亩丝谔栂嗤鴮?dǎo)致連接沖突的問題。
客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會導(dǎo)致端口資源耗盡而無法建立新的連接嗎?要看客戶端是否都是與同一個服務(wù)器(目標(biāo)地址和目標(biāo)端口一樣)建立連接。
如果客戶端都是與同一個服務(wù)器(目標(biāo)地址和目標(biāo)端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當(dāng)端口資源被耗盡,就無法與這個服務(wù)器再建立連接了。即使在這種狀態(tài)下,還是可以與其他服務(wù)器建立連接的,只要客戶端連接的服務(wù)器不是同一個,那么端口是重復(fù)使用的。
如何解決客戶端 TCP 連接 TIME_WAIT 過多,導(dǎo)致無法與同一個服務(wù)器建立連接的問題?打開 net.ipv4.tcp_tw_reuse 這個內(nèi)核參數(shù)。
因?yàn)殚_啟了這個內(nèi)核參數(shù)后,客戶端調(diào)用 connect 函數(shù)時,如果選擇到的端口,已經(jīng)被相同四元組的連接占用的時候,就會判斷該連接是否處于 TIME_WAIT 狀態(tài)。
如果該連接處于 TIME_WAIT 狀態(tài)并且 TIME_WAIT 狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用這個連接,然后就可以正常使用該端口了。
標(biāo)簽:
- 每日快訊!字節(jié)一面:TCP 和 UDP 可以使用同一個端口嗎?
- 天天亮點(diǎn)!5G 及其對物聯(lián)網(wǎng)的影響
- 當(dāng)前信息:企業(yè)實(shí)現(xiàn)數(shù)字化轉(zhuǎn)型需要具備六大能力
- 天天快資訊丨面試突擊:為什么 TCP 需要三次握手?
- 專為創(chuàng)作而生 華碩ProArt 創(chuàng)16 2022正式在京東小魔方發(fā)售
- 天天新消息丨算力發(fā)展未來還需邁過幾道坎兒?
- 全球快資訊丨全屋WiFi覆蓋 mesh、ap面板、電力貓該怎么選?
- 聯(lián)想小新官宣:新款二合一筆記本小新Duet2022將發(fā)布
- 世界微頭條丨面試突擊:說一下 TCP/IP 協(xié)議?你了解每層的作用嗎?
- 環(huán)球要聞:看懂RTK定位,這一篇就夠啦!





















