不卡av在线播放_欧美成人AU在线看_亚洲一区二区 视频_五月天亚洲无码伊人

Article / 文章中心

字節(jié)一面:TCP 和 UDP 可以使用同一個端口嗎?

發(fā)布時間:2022-07-26 點擊數:830
在數據鏈路層中,通過 MAC 地址來尋找局域網中的主機。在網際層中,通過 IP 地址來尋找網絡中互連的主機或路由器。在傳輸層中,需要通過端口進行尋址,來識別同一計算機中同時通信的不同應用程序。

大家好,我是小林。

之前有讀者在字節(jié)面試的時候,被問到:TCP 和 UDP 可以同時監(jiān)聽相同的端口嗎?

圖片

關于端口的知識點,還是挺多可以講的,比如還可以牽扯到這幾個問題:

  • 多個 TCP 服務進程可以同時綁定同一個端口嗎?
  • 客戶端的端口可以重復使用嗎?
  • 客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會導致端口資源耗盡而無法建立新的連接嗎?

所以,這次就跟大家盤一盤這些問題。

TCP 和 UDP 可以同時綁定相同的端口嗎?

其實我感覺這個問題「TCP 和 UDP 可以同時監(jiān)聽相同的端口嗎?」表述有問題,這個問題應該表述成「TCP 和 UDP 可以同時綁定相同的端口嗎?」

因為「監(jiān)聽」這個動作是在 TCP 服務端網絡編程中才具有的,而 UDP 服務端網絡編程中是沒有「監(jiān)聽」這個動作的。

TCP 和 UDP 服務端網絡相似的一個地方,就是會調用 bind 綁定端口。

給大家貼一下  TCP 和 UDP 網絡編程的區(qū)別就知道了。

TCP 網絡編程如下,服務端執(zhí)行 listen() 系統(tǒng)調用就是監(jiān)聽端口的動作。

圖片

TCP 網絡編程

UDP 網絡編程如下,服務端是沒有監(jiān)聽這個動作的,只有執(zhí)行  bind()  系統(tǒng)調用來綁定端口的動作。

圖片

UDP 網絡編程

TCP 和 UDP 可以同時綁定相同的端口嗎?

答案:可以的。

在數據鏈路層中,通過 MAC 地址來尋找局域網中的主機。在網際層中,通過 IP 地址來尋找網絡中互連的主機或路由器。在傳輸層中,需要通過端口進行尋址,來識別同一計算機中同時通信的不同應用程序。

所以,傳輸層的「端口號」的作用,是為了區(qū)分同一個主機上不同應用程序的數據包。

傳輸層有兩個傳輸協(xié)議分別是 TCP 和 UDP,在內核中是兩個完全獨立的軟件模塊。

當主機收到數據包后,可以在 IP 包頭的「協(xié)議號」字段知道該數據包是 TCP/UDP,所以可以根據這個信息確定送給哪個模塊(TCP/UDP)處理,送給 TCP/UDP 模塊的報文根據「端口號」確定送給哪個應用程序處理。

圖片

因此, TCP/UDP 各自的端口號也相互獨立,如 TCP 有一個 80 號端口,UDP 也可以有一個 80 號端口,二者并不沖突。

驗證結果

我簡單寫了 TCP 和 UDP 服務端的程序,它們都綁定同一個端口號 8888。

圖片

運行這兩個程序后,通過 netstat 命令可以看到,TCP 和 UDP 是可以同時綁定同一個端口號的。

圖片

多個 TCP 服務進程可以綁定同一個端口嗎?

還是以前面的 TCP 服務端程序作為例子,啟動兩個同時綁定同一個端口的 TCP 服務進程。

運行第一個  TCP 服務進程之后,netstat 命令可以查看,8888 端口已經被一個 TCP 服務進程綁定并監(jiān)聽了,如下圖:

圖片

接著,運行第二個 TCP 服務進程的時候,就報錯了“Address already in use”,如下圖:

圖片

我上面的測試案例是兩個 TCP 服務進程同時綁定地址和端口是:0.0.0.0 地址和8888端口,所以才出現的錯誤。

如果兩個 TCP 服務進程綁定的 IP 地址不同,而端口相同的話,也是可以綁定成功的,如下圖:

圖片

所以,默認情況下,針對「多個 TCP 服務進程可以綁定同一個端口嗎?」這個問題的答案是:如果兩個 TCP 服務進程同時綁定的 IP 地址和端口都相同,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。

注意,如果 TCP 服務進程 A 綁定的地址是  0.0.0.0 和端口 8888,而如果 TCP 服務進程 B 綁定的地址是 192.168.1.100 地址(或者其他地址)和端口 8888,那么執(zhí)行 bind() 時候也會出錯。

這是因為 0.0.0.0  地址比較特殊,代表任意地址,意味著綁定了 0.0.0.0  地址,相當于把主機上的所有 IP 地址都綁定了。

重啟 TCP 服務進程時,為什么會有“Address in use”的報錯信息?

TCP 服務進程需要綁定一個 IP 地址和一個端口,然后就監(jiān)聽在這個地址和端口上,等待客戶端連接的到來。

然后在實踐中,我們可能會經常碰到一個問題,當 TCP 服務進程重啟之后,總是碰到“Address in use”的報錯信息,TCP 服務進程不能很快地重啟,而是要過一會才能重啟成功。

這是為什么呢?

當我們重啟 TCP 服務進程的時候,意味著通過服務器端發(fā)起了關閉連接操作,于是就會經過四次揮手,而對于主動關閉方,會在 TIME_WAIT 這個狀態(tài)里停留一段時間,這個時間大約為 2MSL。

圖片

當 TCP 服務進程重啟時,服務端會出現 TIME_WAIT 狀態(tài)的連接,TIME_WAIT 狀態(tài)的連接使用的 IP+PORT 仍然被認為是一個有效的 IP+PORT 組合,相同機器上不能夠在該 IP+PORT 組合上進行綁定,那么執(zhí)行 bind() 函數的時候,就會返回了 Address already in use 的錯誤。

而等 TIME_WAIT 狀態(tài)的連接結束后,重啟 TCP 服務進程就能成功。

重啟 TCP 服務進程時,如何避免“Address in use”的報錯信息?

我們可以在調用 bind 前,對 socket 設置 SO_REUSEADDR 屬性,可以解決這個問題。

復制
int on = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));1.2.

因為  SO_REUSEADDR 作用是:如果當前啟動進程綁定的 IP+PORT 與處于TIME_WAIT 狀態(tài)的連接占用的 IP+PORT 存在沖突,但是新啟動的進程使用了 SO_REUSEADDR 選項,那么該進程就可以綁定成功。

舉個例子,服務端有個監(jiān)聽 0.0.0.0 地址和 8888 端口的 TCP 服務進程。?

圖片

有個客戶端(IP地址:192.168.1.100)已經和服務端(IP 地址:172.19.11.200)建立了 TCP 連接,那么在 TCP 服務進程重啟時,服務端會與客戶端經歷四次揮手,服務端的 TCP 連接會短暫處于 TIME_WAIT 狀態(tài):

復制
客戶端地址:端口           服務端地址:端口        TCP 連接狀態(tài)192.168.1.100:37272     172.19.11.200:8888    TIME_WAIT1.2.

如果 TCP 服務進程沒有對 socket 設置 SO_REUSEADDR 屬性,那么在重啟時,由于存在一個和綁定 IP+PORT 一樣的 TIME_WAIT 狀態(tài)的連接,那么在執(zhí)行 bind() 函數的時候,就會返回了 Address already in use 的錯誤。

如果 TCP 服務進程對 socket 設置 SO_REUSEADDR 屬性了,那么在重啟時,即使存在一個和綁定 IP+PORT 一樣的 TIME_WAIT 狀態(tài)的連接,依然可以正常綁定成功,因此可以正常重啟成功。

因此,在所有 TCP 服務器程序中,調用 bind 之前最好對 socket 設置 SO_REUSEADDR 屬性,這不會產生危害,相反,它會幫助我們在很快時間內重啟服務端程序。?

前面我提到過這個問題:如果 TCP 服務進程 A 綁定的地址是  0.0.0.0 和端口 8888,而如果 TCP 服務進程 B 綁定的地址是 192.168.1.100 地址(或者其他地址)和端口 8888,那么執(zhí)行 bind() 時候也會出錯。

這個問題也可以由 SO_REUSEADDR 解決,因為它的另外一個作用是:綁定的 IP地址 + 端口時,只要 IP 地址不是正好(exactly)相同,那么允許綁定。

比如,0.0.0.0:8888 和192.168.1.100:8888,雖然邏輯意義上前者包含了后者,但是 0.0.0.0 泛指所有本地 IP,而 192.168.1.100 特指某一IP,兩者并不是完全相同,所以在對 socket 設置 SO_REUSEADDR 屬性后,那么執(zhí)行 bind() 時候就會綁定成功。

客戶端的端口可以重復使用嗎?

客戶端在執(zhí)行 connect 函數的時候,會在內核里隨機選擇一個端口,然后向服務端發(fā)起 SYN 報文,然后與服務端進行三次握手。

圖片

所以,客戶端的端口選擇的發(fā)生在 connect 函數,內核在選擇端口的時候,會從 net.ipv4.ip_local_port_range 這個內核參數指定的范圍來選取一個端口作為客戶端端口。

該參數的默認值是 32768 61000,意味著端口總可用的數量是 61000 - 32768 = 28232 個。

當客戶端與服務端完成 TCP 連接建立后,我們可以通過 netstat 命令查看 TCP 連接。

復制
$ netstat -napt
協(xié)議  源ip地址:端口            目的ip地址:端口         狀態(tài)
tcp  192.168.110.182.64992   117.147.199.51.443     ESTABLISHED1.2.3.


那問題來了,上面客戶端已經用了 64992 端口,那么還可以繼續(xù)使用該端口發(fā)起連接嗎?


這個問題,很多同學都會說不可以繼續(xù)使用該端口了,如果按這個理解的話, 默認情況下客戶端可以選擇的端口是 28232 個,那么意味著客戶端只能最多建立  28232 個 TCP 連接,如果真是這樣的話,那么這個客戶端并發(fā)連接也太少了吧,所以這是錯誤理解。

正確的理解是,TCP 連接是由四元組(源IP地址,源端口,目的IP地址,目的端口)唯一確認的,那么只要四元組中其中一個元素發(fā)生了變化,那么就表示不同的 TCP 連接的。所以如果客戶端已使用端口 64992 與服務端 A 建立了連接,那么客戶端要與服務端 B 建立連接,還是可以使用端口 64992 的,因為內核是通過四元祖信息來定位一個 TCP 連接的,并不會因為客戶端的端口號相同,而導致連接沖突的問題。

比如下面這張圖,有 2 個 TCP 連接,左邊是客戶端,右邊是服務端,客戶端使用了相同的端口 50004 與兩個服務端建立了 TCP 連接。

圖片

仔細看,上面這兩條 TCP 連接的四元組信息中的「目的 IP 地址」是不同的,一個是 180.101.49.12 ,另外一個是 180.101.49.11。

多個客戶端可以 bind 同一個端口嗎?

bind 函數雖然常用于服務端網絡編程中,但是它也是用于客戶端的。

前面我們知道,客戶端是在調用 connect 函數的時候,由內核隨機選取一個端口作為連接的端口。

而如果我們想自己指定連接的端口,就可以用 bind 函數來實現:客戶端先通過 bind 函數綁定一個端口,然后調用 connect 函數就會跳過端口選擇的過程了,轉而使用 bind 時確定的端口。

針對這個問題:多個客戶端可以 bind 同一個端口嗎?

要看多個客戶端綁定的 IP + PORT 是否都相同,如果都是相同的,那么在執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。

如果一個綁定在 192.168.1.100:6666,一個綁定在 192.168.1.200:6666,因為 IP 不相同,所以執(zhí)行 bind() 的時候,能正常綁定。

所以, 如果多個客戶端同時綁定的 IP 地址和端口都是相同的,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。

一般而言,客戶端不建議使用 bind 函數,應該交由 connect 函數來選擇端口會比較好,因為客戶端的端口通常都沒什么意義。

客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會導致端口資源耗盡而無法建立新的連接嗎?

針對這個問題要看,客戶端是否都是與同一個服務器(目標地址和目標端口一樣)建立連接。

如果客戶端都是與同一個服務器(目標地址和目標端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當端口資源被耗盡,就無法與這個服務器再建立連接了。

但是,因為只要客戶端連接的服務器不同,端口資源可以重復使用的。

所以,如果客戶端都是與不同的服務器建立連接,即使客戶端端口資源只有幾萬個, 客戶端發(fā)起百萬級連接也是沒問題的(當然這個過程還會受限于其他資源,比如文件描述符、內存、CPU 等)。

如何解決客戶端 TCP 連接 TIME_WAIT 過多,導致無法與同一個服務器建立連接的問題?

前面我們提到,如果客戶端都是與同一個服務器(目標地址和目標端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當端口資源被耗盡,就無法與這個服務器再建立連接了。

針對這個問題,也是有解決辦法的,那就是打開 net.ipv4.tcp_tw_reuse  這個內核參數。

因為開啟了這個內核參數后,客戶端調用 connect  函數時,如果選擇到的端口,已經被相同四元組的連接占用的時候,就會判斷該連接是否處于  TIME_WAIT 狀態(tài),如果該連接處于 TIME_WAIT 狀態(tài)并且 TIME_WAIT 狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用這個連接,然后就可以正常使用該端口了。

舉個例子,假設客戶端已經與服務器建立了一個 TCP 連接,并且這個狀態(tài)處于  TIME_WAIT 狀態(tài):

復制
客戶端地址:端口           服務端地址:端口         TCP 連接狀態(tài)192.168.1.100:2222      172.19.11.21:8888     TIME_WAIT1.2.

然后客戶端又與該服務器(172.19.11.21:8888)發(fā)起了連接,在調用 connect 函數時,內核剛好選擇了 2222 端口,接著發(fā)現已經被相同四元組的連接占用了:

  • 如果沒有開啟net.ipv4.tcp_tw_reuse  內核參數,那么內核就會選擇下一個端口,然后繼續(xù)判斷,直到找到一個沒有被相同四元組的連接使用的端口, 如果端口資源耗盡還是沒找到,那么 connect 函數就會返回錯誤。
  • 如果開啟了 net.ipv4.tcp_tw_reuse  內核參數,就會判斷該四元組的連接狀態(tài)是否處于 TIME_WAIT 狀態(tài),如果連接處于 TIME_WAIT 狀態(tài)并且該狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用該連接,于是就可以使用 2222 端口了,這時 connect 就會返回成功。

再次提醒一次,開啟了 net.ipv4.tcp_tw_reuse  內核參數,是客戶端(連接發(fā)起方) 在調用 connect() 函數時才起作用,所以在服務端開啟這個參數是沒有效果的。

客戶端端口選擇的流程總結

至此,我們已經把客戶端在執(zhí)行 connect 函數時,內核選擇端口的情況大致說了一遍,為了讓大家更明白客戶端端口的選擇過程,我畫了一流程圖。

圖片

總結

TCP 和 UDP 可以同時綁定相同的端口嗎?

可以的。

TCP 和 UDP 傳輸協(xié)議,在內核中是由兩個完全獨立的軟件模塊實現的。

當主機收到數據包后,可以在 IP 包頭的「協(xié)議號」字段知道該數據包是 TCP/UDP,所以可以根據這個信息確定送給哪個模塊(TCP/UDP)處理,送給 TCP/UDP 模塊的報文根據「端口號」確定送給哪個應用程序處理。

因此, TCP/UDP 各自的端口號也相互獨立,互不影響。

多個 TCP 服務進程可以同時綁定同一個端口嗎?

如果兩個 TCP 服務進程同時綁定的 IP 地址和端口都相同,那么執(zhí)行 bind() 時候就會出錯,錯誤是“Address already in use”。

如果兩個 TCP 服務進程綁定的端口都相同,而 IP 地址不同,那么執(zhí)行 bind() 不會出錯。

如何解決服務端重啟時,報錯“Address already in use”的問題?

當我們重啟 TCP 服務進程的時候,意味著通過服務器端發(fā)起了關閉連接操作,于是就會經過四次揮手,而對于主動關閉方,會在 TIME_WAIT 這個狀態(tài)里停留一段時間,這個時間大約為 2MSL。

當 TCP 服務進程重啟時,服務端會出現 TIME_WAIT 狀態(tài)的連接,TIME_WAIT 狀態(tài)的連接使用的 IP+PORT 仍然被認為是一個有效的 IP+PORT 組合,相同機器上不能夠在該 IP+PORT 組合上進行綁定,那么執(zhí)行 bind() 函數的時候,就會返回了 Address already in use 的錯誤。

要解決這個問題,我們可以對 socket 設置 SO_REUSEADDR 屬性。

這樣即使存在一個和綁定 IP+PORT 一樣的 TIME_WAIT 狀態(tài)的連接,依然可以正常綁定成功,因此可以正常重啟成功。

客戶端的端口可以重復使用嗎?

在客戶端執(zhí)行 connect 函數的時候,只要客戶端連接的服務器不是同一個,內核允許端口重復使用。

TCP 連接是由四元組(源IP地址,源端口,目的IP地址,目的端口)唯一確認的,那么只要四元組中其中一個元素發(fā)生了變化,那么就表示不同的 TCP 連接的。

所以,如果客戶端已使用端口 64992 與服務端 A 建立了連接,那么客戶端要與服務端 B 建立連接,還是可以使用端口 64992 的,因為內核是通過四元祖信息來定位一個 TCP 連接的,并不會因為客戶端的端口號相同,而導致連接沖突的問題。

客戶端 TCP 連接 TIME_WAIT 狀態(tài)過多,會導致端口資源耗盡而無法建立新的連接嗎?

要看客戶端是否都是與同一個服務器(目標地址和目標端口一樣)建立連接。

如果客戶端都是與同一個服務器(目標地址和目標端口一樣)建立連接,那么如果客戶端 TIME_WAIT 狀態(tài)的連接過多,當端口資源被耗盡,就無法與這個服務器再建立連接了。即使在這種狀態(tài)下,還是可以與其他服務器建立連接的,只要客戶端連接的服務器不是同一個,那么端口是重復使用的。

如何解決客戶端 TCP 連接 TIME_WAIT 過多,導致無法與同一個服務器建立連接的問題?

打開 net.ipv4.tcp_tw_reuse  這個內核參數。

因為開啟了這個內核參數后,客戶端調用 connect  函數時,如果選擇到的端口,已經被相同四元組的連接占用的時候,就會判斷該連接是否處于  TIME_WAIT 狀態(tài)。

如果該連接處于 TIME_WAIT 狀態(tài)并且 TIME_WAIT 狀態(tài)持續(xù)的時間超過了 1 秒,那么就會重用這個連接,然后就可以正常使用該端口了。