SO_REUSEADDR SO_REUSEPORT 解析
首先我們需要了解一些基本知識(shí),一個(gè)TCP/UDP連接是被一個(gè)五元組確定的{源地址,源端口,協(xié)議,目的端口,目的地址}。
因此,任何兩個(gè)連接都不可能擁有相同的五元組,否則系統(tǒng)將無(wú)法區(qū)別這兩個(gè)連接。
當(dāng)使用socket()函數(shù)創(chuàng)建套接字的時(shí)候,我們就指定了該套接字使用的protocol(協(xié)議),bind()函數(shù)設(shè)置了源地址和源端口號(hào),而目的地址和目的端口號(hào)則由connect()函數(shù)設(shè)定。
盡管允許對(duì)UDP進(jìn)行"連接",但由于UDP是一個(gè)無(wú)連接協(xié)議,UDP套接字仍然可以不經(jīng)連接就使用。"未連接"的UDP套接字在數(shù)據(jù)被第一次發(fā)送之前并不會(huì)綁定,只有在發(fā)送的時(shí)候被系統(tǒng)自動(dòng)綁定,因此未綁定的UDP套接字也就無(wú)法收到(回復(fù))數(shù)據(jù)。未綁定的TCP也一樣,它將在連接的時(shí)候自動(dòng)綁定。
如果你明確綁定一個(gè)socket,把它綁定到端口0是可行的,它意味著"any port"("任意端口")。由于一個(gè)套接字無(wú)法真正的被綁定到系統(tǒng)上的所有端口,那么在這種情況下系統(tǒng)將不得不選擇一個(gè)具體的端口號(hào)。源地址使用類(lèi)似的通配符,也就是"0.0.0.0" 。和端口不同的是,一個(gè)套接字可以被綁定到任意地址,這里指本地網(wǎng)絡(luò)接口的所有地址。由于socket無(wú)法在連接的時(shí)候同時(shí)綁定到所有源IP地址,因此當(dāng)接下來(lái)有一個(gè)連接過(guò)來(lái)的時(shí)候,系統(tǒng)將不得不挑選一個(gè)源IP地址。考慮到目的地址和路由表中的路由信息,系統(tǒng)將會(huì)選擇一個(gè)合適的源地址,并將任意地址替換為一個(gè)選定的地址作為源地址。
默認(rèn)情況下,任意兩個(gè)socket都無(wú)法綁定到相同的源IP地址和源端口(即源地址和源端口號(hào)均相同)。只要源端口號(hào)不相同,那么源地址實(shí)際上沒(méi)什么關(guān)系。將socket A綁定到地址A和端口X (A:X),socket B綁定到地址B和端口Y (B:Y),只要X != Y,那么這種綁定都是可行的。然而當(dāng)X==Y的時(shí)候只要A != B,這種綁定方式也仍然可行。記住:一個(gè)socket可能綁定到本地"any address"。例如一個(gè)socket綁定為 0.0.0.0:21,那么它同時(shí)綁定了所有的本地地址,在這種情況下,不論其它的socket選擇什么特定的IP地址,它們都無(wú)法綁定到21端口,因?yàn)?.0.0.0和所有的本地地址都會(huì)沖突。
SO_REUSEADDR
如果在綁定一個(gè)socket之前設(shè)置了SO_REUSEADDR,除非兩個(gè)socket綁定的源地址和端口號(hào)都一樣,那么這兩個(gè)綁定都是可行的。也許你會(huì)疑惑這跟之前的有什么不一樣?關(guān)鍵是SO_REUSEADDR改變了在處理源地址沖突時(shí)對(duì)通配地址的處理方式。
當(dāng)沒(méi)有設(shè)置SO_REUSEADDR的時(shí)候,socket A先綁定到0.0.0.0:21,然后socket B綁定到192.168.0.1:21的時(shí)候?qū)?huì)失敗(EADDRINUSE錯(cuò)誤),因?yàn)?.0.0.0意味著"任意本地IP地址”,也就是"所有本地IP地址“,因此包括192.168.0.1在內(nèi)的所有IP地址都被認(rèn)為是已經(jīng)使用了。但是在設(shè)置SO_REUSEADDR之后socket B的綁定將會(huì)成功,因?yàn)?.0.0.0和192.168.0.1事實(shí)上不是同一個(gè)IP地址,一個(gè)是代表所有地址的通配地址,另一個(gè)是一個(gè)具體的地址。注意上面的表述對(duì)于socket A和socket B的綁定順序是無(wú)關(guān)的,沒(méi)有設(shè)置SO_REUSEADDR,它們將失敗,設(shè)置了SO_REUSEADDR,它將成功。
下面給出了一個(gè)表格列出了所有的可能組合:
?
SO_REUSEPORT
SO_REUSEPORT的含義與絕大部分人對(duì)SO_REUSEADDR的理解一樣。基本上說(shuō)來(lái),SO_REUSEPORT允許你將多個(gè)socket綁定到相同的地址和端口只要它們?cè)诮壎ㄖ岸荚O(shè)置了SO_REUSEPORT。如果第一個(gè)綁定某個(gè)地址和端口的socket沒(méi)有設(shè)置SO_REUSEPORT,那么其他的socket無(wú)論有沒(méi)有設(shè)置SO_REUSEPORT都無(wú)法綁定到該地址和端口直到第一個(gè)socket釋放了綁定。? ? SO_REUSEPORT并不表示SO_REUSEADDR。這意味著如果一個(gè)socket在綁定時(shí)沒(méi)有設(shè)置SO_REUSEPORT,那么同預(yù)期的一樣,其它的socket對(duì)相同地址和端口的綁定會(huì)失敗,但是如果綁定相同地址和端口的socket正處在TIME_WAIT狀態(tài),新的綁定也會(huì)失敗。當(dāng)有個(gè)socket綁定后處在TIME_WAIT狀態(tài)(釋放時(shí))時(shí),為了使得其它socket綁定相同地址和端口能夠成功,需要設(shè)置SO_REUSEADDR或者在這兩個(gè)socket上都設(shè)置SO_REUSEPORT。當(dāng)然,在socket上同時(shí)設(shè)置SO_REUSEPORT和SO_REUSEADDR也是可行的。
? ? 關(guān)于SO_REUSEPORT除了它在被添加到系統(tǒng)的時(shí)間比SO_REUSEPORT晚就沒(méi)有其它需要說(shuō)的了,這也是為什么在有些系統(tǒng)的socket實(shí)現(xiàn)上你找不到這個(gè)選項(xiàng),因?yàn)檫@些系統(tǒng)的代碼都是在這個(gè)選項(xiàng)被添加到BSD之前fork了BSD,這樣就不能將兩個(gè)socket綁定到真正相同的“地址” (address+port)。
Connect() Returning EADDRINUSE?
? ? ? 絕大部分人都知道bind()可能失敗返回EADDRINUSE,然而當(dāng)你開(kāi)始使用地址重用,你可能會(huì)碰到奇怪的情況:connect()失敗返回同樣的錯(cuò)誤EADDRINUSE。怎么會(huì)出現(xiàn)這種情況了? 一個(gè)遠(yuǎn)端地址畢竟是connect添加到socket上的,怎么會(huì)已經(jīng)被使用了? 將多個(gè)socket連接到相同的遠(yuǎn)端地址從來(lái)沒(méi)有出現(xiàn)過(guò)這樣的情況,這是為什么了?? ? ?正如我在開(kāi)頭說(shuō)過(guò)的,一個(gè)連接是被一個(gè)五元組定義的。同樣我也說(shuō)了任意兩個(gè)連接的五元組不能完全一樣,因?yàn)檫@樣的話內(nèi)核就沒(méi)辦法區(qū)分這兩個(gè)連接了。然而,在地址重用的情況下,你可以把同協(xié)議的兩個(gè)socket綁定到完全相同的源地址和源端口,這意味著五元組中已經(jīng)有三個(gè)元素相同了(協(xié)議,源地址,源端口)。如果你嘗試將這些socket連接到同樣的目的地址和目的端口,你就創(chuàng)建了兩個(gè)完全相同的連接。這是不行的,至少對(duì)TCP不行(UDP實(shí)際上沒(méi)有真實(shí)的連接)。如果數(shù)據(jù)到達(dá)這兩個(gè)連接中的任何一個(gè),那么系統(tǒng)將無(wú)法區(qū)分?jǐn)?shù)據(jù)到底屬于誰(shuí)。因此當(dāng)源地址和源端口相同時(shí),目的地址或者目的端口必須不同,否則內(nèi)核無(wú)法進(jìn)行區(qū)分,這種情況下,connect()將在第二個(gè)socket嘗試連接時(shí)返回EADDRINUSE。 ?Linux 3.9加入了SO_REUSEPORT。這個(gè)選項(xiàng)允許多個(gè)socket(TCP or UDP)不管是監(jiān)聽(tīng)socket還是非監(jiān)聽(tīng)socket只要都在綁定之前都設(shè)置了它,那么就可以綁定到完全相同的地址和端口。為了阻止"port 劫持"有一個(gè)特別的限制:所有希望共享源地址和端口的socket都必須擁有相同的有效用戶id。因此一個(gè)用戶就不能從另一個(gè)用戶那里"偷取"端口。另外,內(nèi)核在處理SO_REUSEPORT socket的時(shí)候使用了其它系統(tǒng)上沒(méi)有用到的"特別魔法":對(duì)于UDP socket,內(nèi)核嘗試平均的轉(zhuǎn)發(fā)數(shù)據(jù)報(bào),對(duì)于TCP監(jiān)聽(tīng)socket,內(nèi)核嘗試將新的客戶連接請(qǐng)求(由accept返回)平均的交給共享同一地址和端口的socket(監(jiān)聽(tīng)socket)。這意味著在其他系統(tǒng)上socket收到一個(gè)數(shù)據(jù)報(bào)或連接請(qǐng)求或多或少是隨機(jī)的,但是linux嘗試優(yōu)化分配。例如:一個(gè)簡(jiǎn)單的服務(wù)器程序的多個(gè)實(shí)例可以使用SO_REUSEPORT socket實(shí)現(xiàn)一個(gè)簡(jiǎn)單的負(fù)載均衡,因?yàn)閮?nèi)核已經(jīng)把復(fù)制的分配都做了。 2015年12月15日00:34:22
總結(jié)
以上是生活随笔為你收集整理的SO_REUSEADDR SO_REUSEPORT 解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 深度解析PHP数组函数array_chu
- 下一篇: [bzoj1187][HNOI2007]