日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

SO_REUSEADDR SO_REUSEPORT 解析

發布時間:2025/3/15 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SO_REUSEADDR SO_REUSEPORT 解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

首先我們需要了解一些基本知識,一個TCP/UDP連接是被一個五元組確定的{源地址,源端口,協議,目的端口,目的地址}。

因此,任何兩個連接都不可能擁有相同的五元組,否則系統將無法區別這兩個連接。

當使用socket()函數創建套接字的時候,我們就指定了該套接字使用的protocol(協議),bind()函數設置了源地址和源端口號,而目的地址和目的端口號則由connect()函數設定。

盡管允許對UDP進行"連接",但由于UDP是一個無連接協議,UDP套接字仍然可以不經連接就使用。"未連接"的UDP套接字在數據被第一次發送之前并不會綁定,只有在發送的時候被系統自動綁定,因此未綁定的UDP套接字也就無法收到(回復)數據。未綁定的TCP也一樣,它將在連接的時候自動綁定。

如果你明確綁定一個socket,把它綁定到端口0是可行的,它意味著"any port"("任意端口")。由于一個套接字無法真正的被綁定到系統上的所有端口,那么在這種情況下系統將不得不選擇一個具體的端口號。源地址使用類似的通配符,也就是"0.0.0.0" 。和端口不同的是,一個套接字可以被綁定到任意地址,這里指本地網絡接口的所有地址由于socket無法在連接的時候同時綁定到所有源IP地址,因此當接下來有一個連接過來的時候,系統將不得不挑選一個源IP地址。考慮到目的地址和路由表中的路由信息,系統將會選擇一個合適的源地址,并將任意地址替換為一個選定的地址作為源地址。

默認情況下,任意兩個socket都無法綁定到相同的源IP地址和源端口(即源地址和源端口號均相同)。只要源端口號不相同,那么源地址實際上沒什么關系。將socket A綁定到地址A和端口X (A:X),socket B綁定到地址B和端口Y (B:Y),只要X != Y,那么這種綁定都是可行的。然而當X==Y的時候只要A != B,這種綁定方式也仍然可行。記住:一個socket可能綁定到本地"any address"。例如一個socket綁定為 0.0.0.0:21,那么它同時綁定了所有的本地地址,在這種情況下,不論其它的socket選擇什么特定的IP地址,它們都無法綁定到21端口,因為0.0.0.0和所有的本地地址都會沖突。

SO_REUSEADDR

如果在綁定一個socket之前設置了SO_REUSEADDR,除非兩個socket綁定的源地址和端口號都一樣,那么這兩個綁定都是可行的。也許你會疑惑這跟之前的有什么不一樣?關鍵是SO_REUSEADDR改變了在處理源地址沖突時對通配地址的處理方式。

當沒有設置SO_REUSEADDR的時候,socket A先綁定到0.0.0.0:21,然后socket B綁定到192.168.0.1:21的時候將會失敗(EADDRINUSE錯誤),因為0.0.0.0意味著"任意本地IP地址”,也就是"所有本地IP地址“,因此包括192.168.0.1在內的所有IP地址都被認為是已經使用了。但是在設置SO_REUSEADDR之后socket B的綁定將會成功,因為0.0.0.0和192.168.0.1事實上不是同一個IP地址,一個是代表所有地址的通配地址,另一個是一個具體的地址。注意上面的表述對于socket A和socket B的綁定順序是無關的,沒有設置SO_REUSEADDR,它們將失敗,設置了SO_REUSEADDR,它將成功。

下面給出了一個表格列出了所有的可能組合:
?

SO_REUSEADDR socket A socket B Result ---------------------------------------------------------------------ON/OFF 192.168.0.1:21 192.168.0.1:21 Error (EADDRINUSE)ON/OFF 192.168.0.1:21 10.0.0.1:21 OKON/OFF 10.0.0.1:21 192.168.0.1:21 OKOFF 0.0.0.0:21 192.168.1.0:21 Error (EADDRINUSE)OFF 192.168.1.0:21 0.0.0.0:21 Error (EADDRINUSE)ON 0.0.0.0:21 192.168.1.0:21 OKON 192.168.1.0:21 0.0.0.0:21 OKON/OFF 0.0.0.0:21 0.0.0.0:21 Error (EADDRINUSE) 現在我們知道SO_REUSEADDR對通配地址有影響,但這不是它唯一影響到的方面。還有一個眾所周知的影響同時也是大多數人在服務器程序上使用SO_REUSEADDR的首要原因。為了了解其它SO_REUSEADDR重要的使用方式,我們需要深入了解TCP協議的工作方式。 一個socket有一個發送緩沖區,當調用send()函數成功后,這并不意味著所有數據都真正被發送出去了,它只意味著數據都被送到了發送緩沖區中。對于UDP socket來說,如果不是立刻發送的話,數據通常也會很快的發送出去,但對于TCP socket,在數據加入到緩沖區和真正被發送出去之間的時延會相當長。這就導致當我們close一個TCP socket的時候,可能在發送緩沖區中保存著等待發送的數據(由于send()成功返回,因此你也許認為數據已經被發送了)。如果TCP的實現是立刻關閉socket,那么所有這些數據都會丟失而你的程序根本不可能知道。TCP被稱為可靠協議,像這種丟失數據的方式就不那么可靠了。這也是為什么當我們close一個TCP socket的時候,如果它仍然有數據等待發送,那么該socket會進入TIME_WAIT狀態(一般都是在服務器端主動關閉socket的情況下會發生)。這種狀態將持續到數據被全部發送或者發生超時。 在內核徹底關閉socket之前等待的總時間(不管是否有數據在發送緩沖區中等待發送)叫做Linger Time。Linger Time在大部分系統上都是一個全局性的配置項而且在默認情況下時間相當長(在大部分系統上是兩分鐘)。當然對于每個socket我們也可以使用socket選項SO_LINGER進行配置,可以將等待時間設置的更長一點兒或更短一點兒甚至禁用它。禁用Linger Time絕對是一個壞主意,雖然優雅的關閉socket是一個稍微復雜的過程并且涉及到來回的發送數據包(以及在數據包丟失后重發它們),并且這個過程還受到Linger Time的限制。如果禁用Linger Time,socket可能丟失的不僅僅是待發送的數據,而且還會粗暴的關閉socket,在絕大部分情況下,都不應該這樣使用。而且如果你用SO_LINGER禁用了Linger Time,而你的程序在顯式的關閉socket之前就終止的話,BSD仍然會等待,而不管已經禁用了它。這種情況的一個例子就是你的程序調用了exit() 、或者進程被信號殺死。這樣的話,不管在什么情況下,你都無法對某一個socket禁用linger了。 ?問題在于,系統是怎樣看待TIME_WAIT狀態的?如果SO_REUSEADDR還沒有設置,一個處在TIME_WAIT的socket仍然被認為綁定在源地址和端口,任何其它的試圖在同樣的地址和端口上綁定一個socket行為都會失敗直到原來的socket真正的關閉了,這通常需要等待Linger Time的時長。所以不要指望在一個socket關閉后立刻將源地址和端口綁定到新的socket上,在絕大部分情況下,這種行為都會失敗。然而,在設置了SO_REUSEADDR之后試圖這樣綁定(綁定相同的地址和端口)僅僅只會被忽略,而且你可以將相同的地址綁定到不同的socket上。注意當一個socket處于TIME_WAIT狀態,而你試圖將它綁定到相同的地址和端口,這會導致未預料的結果,因為處于TIME_WAIT狀態的socket仍在"工作",幸運的是這種情況極少發生。 ?對于SO_REUSEADDR你需要知道的最后一點是只有在你想綁定的socket開啟了地址重用之后上面的才會生效,不過這并不需要檢查之前已經綁定或處于TIME_WAIT的socket在它們綁定的時候是否也設置這個選項。也就是說,綁定的成功與否只會檢查當前bind的socket是否開啟了這個標志,不會查看其它的socket。

SO_REUSEPORT

SO_REUSEPORT的含義與絕大部分人對SO_REUSEADDR的理解一樣。基本上說來,SO_REUSEPORT允許你將多個socket綁定到相同的地址和端口只要它們在綁定之前都設置了SO_REUSEPORT。如果第一個綁定某個地址和端口的socket沒有設置SO_REUSEPORT,那么其他的socket無論有沒有設置SO_REUSEPORT都無法綁定到該地址和端口直到第一個socket釋放了綁定。
? ? SO_REUSEPORT并不表示SO_REUSEADDR。這意味著如果一個socket在綁定時沒有設置SO_REUSEPORT,那么同預期的一樣,其它的socket對相同地址和端口的綁定會失敗,但是如果綁定相同地址和端口的socket正處在TIME_WAIT狀態,新的綁定也會失敗。當有個socket綁定后處在TIME_WAIT狀態(釋放時)時,為了使得其它socket綁定相同地址和端口能夠成功,需要設置SO_REUSEADDR或者在這兩個socket上都設置SO_REUSEPORT。當然,在socket上同時設置SO_REUSEPORT和SO_REUSEADDR也是可行的。
? ? 關于SO_REUSEPORT除了它在被添加到系統的時間比SO_REUSEPORT晚就沒有其它需要說的了,這也是為什么在有些系統的socket實現上你找不到這個選項,因為這些系統的代碼都是在這個選項被添加到BSD之前fork了BSD,這樣就不能將兩個socket綁定到真正相同的“地址” (address+port)。

Connect() Returning EADDRINUSE?

? ? ? 絕大部分人都知道bind()可能失敗返回EADDRINUSE,然而當你開始使用地址重用,你可能會碰到奇怪的情況:connect()失敗返回同樣的錯誤EADDRINUSE。怎么會出現這種情況了? 一個遠端地址畢竟是connect添加到socket上的,怎么會已經被使用了? 將多個socket連接到相同的遠端地址從來沒有出現過這樣的情況,這是為什么了?
? ? ?正如我在開頭說過的,一個連接是被一個五元組定義的。同樣我也說了任意兩個連接的五元組不能完全一樣,因為這樣的話內核就沒辦法區分這兩個連接了。然而,在地址重用的情況下,你可以把同協議的兩個socket綁定到完全相同的源地址和源端口,這意味著五元組中已經有三個元素相同了(協議,源地址,源端口)。如果你嘗試將這些socket連接到同樣的目的地址和目的端口,你就創建了兩個完全相同的連接。這是不行的,至少對TCP不行(UDP實際上沒有真實的連接)。如果數據到達這兩個連接中的任何一個,那么系統將無法區分數據到底屬于誰。因此當源地址和源端口相同時,目的地址或者目的端口必須不同,否則內核無法進行區分,這種情況下,connect()將在第二個socket嘗試連接時返回EADDRINUSE。
?Linux 3.9加入了SO_REUSEPORT。這個選項允許多個socket(TCP or UDP)不管是監聽socket還是非監聽socket只要都在綁定之前都設置了它,那么就可以綁定到完全相同的地址和端口。為了阻止"port 劫持"有一個特別的限制:所有希望共享源地址和端口的socket都必須擁有相同的有效用戶id。因此一個用戶就不能從另一個用戶那里"偷取"端口。另外,內核在處理SO_REUSEPORT socket的時候使用了其它系統上沒有用到的"特別魔法":對于UDP socket,內核嘗試平均的轉發數據報,對于TCP監聽socket,內核嘗試將新的客戶連接請求(由accept返回)平均的交給共享同一地址和端口的socket(監聽socket)。這意味著在其他系統上socket收到一個數據報或連接請求或多或少是隨機的,但是linux嘗試優化分配。例如:一個簡單的服務器程序的多個實例可以使用SO_REUSEPORT socket實現一個簡單的負載均衡,因為內核已經把復制的分配都做了。 2015年12月15日00:34:22

總結

以上是生活随笔為你收集整理的SO_REUSEADDR SO_REUSEPORT 解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。