SO_REUSEADDR和SO_REUSEPORT區(qū)別

內(nèi)容來(lái)源于StackOverflow的精彩回答,StackOverflow.

以BSD系統(tǒng)為例。
首先,一個(gè)TCP/UDP連接(Connection)的id,就是由下面五個(gè)值組成元組。

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

任何合法的五個(gè)值的組合都可以定義一個(gè)連接,同時(shí),沒(méi)有任何兩個(gè)連接具有完全相同的元組。

第一個(gè)值protocol是在socket()設(shè)定的,src addrsrc port是在bind()的時(shí)候設(shè)定的,dest addrdest port是在connect()的時(shí)候設(shè)定的。雖然UDP是一個(gè)無(wú)連接協(xié)議,并不需要connect(),但是在第一次發(fā)送數(shù)據(jù)的時(shí)候,UDP connection還是被系統(tǒng)非顯式地綁定到了dest addr / port上。

當(dāng)綁定ip的時(shí)候,可以通過(guò)綁定到0.0.0.0: port來(lái)綁定到所有本地網(wǎng)絡(luò)地址的對(duì)應(yīng)端口上,也可以綁定到192.168.0.100: port來(lái)綁定到特定本地網(wǎng)絡(luò)地址(回環(huán))的特定端口。

在默認(rèn)設(shè)置下,沒(méi)有socket能夠綁定到同一地址的同一端口。比如在Socket A已經(jīng)綁定了0.0.0.0:8000以后,Socket B若是想要綁定192.168.0.100:8000,那就會(huì)報(bào)EADDRINUSE。因?yàn)?strong>Socket A已經(jīng)綁定了所有ip地址的8000端口,包括192.168.0.100:8000。

SO_REUSEADDR

作用一

在為Socket B設(shè)置了SO_REUSEADDR以后,判斷沖突的方式就變了。只要地址不是正好(exactly)相同,那么多個(gè)Socket就能綁定到同一ip上。比如0.0.0.0192.168.0.100,雖然邏輯意義上前者包含了后者,但是0.0.0.0泛指所有本地ip,而192.168.0.100特指某一ip,兩者并不是完全相同,所以Socket B嘗試綁定的時(shí)候,不會(huì)再報(bào)EADDRINUSE,而是綁定成功。
下面是測(cè)試不同設(shè)置下的綁定情況:

SO_REUSEADDR socketA socketB 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 OK
ON/OFF 10.0.0.1:21 192.168.0.1:21 OK
OFF 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 OK
ON 192.168.1.0:21 0.0.0.0:21 OK
ON/OFF 0.0.0.0:21 0.0.0.0:21 Error (EADDRINUSE)

可以看到,如果想綁定addr字符串完全相同的ip,那么無(wú)論SO_REUSEADDR設(shè)置與否,都會(huì)報(bào)地址已使用。但是在設(shè)置了SO_REUSEADDR以后,就可以同時(shí)綁定0.0.0.0192.168.1.0兩個(gè)地址了。

作用二

SO_REUSEADDR的另一個(gè)作用是,可以綁定TIME_WAIT狀態(tài)的地址。

TCP Socket的send()是一個(gè)異步調(diào)用,當(dāng)數(shù)據(jù)送入socket send buffer以后就會(huì)返回。也就是說(shuō),在send()返回以后,數(shù)據(jù)仍然需要經(jīng)歷漫長(zhǎng)的tcp擁塞控制沖突避免等過(guò)程,才能被成功發(fā)送。在沒(méi)有TIME_WAIT狀態(tài)的前提下,假如這個(gè)時(shí)候上層程序判斷通信完成,關(guān)閉了socket,那么緩沖區(qū)的數(shù)據(jù)就會(huì)丟失。所以TIME_WAIT這個(gè)狀態(tài)就被用來(lái)保證,socket能夠續(xù)命到buffer中的數(shù)據(jù)能夠全部發(fā)送完成或者超時(shí)。

TIME_WAIT的時(shí)間,也就是超時(shí)的時(shí)間取決于一個(gè)配置項(xiàng)Linger Time。在大多數(shù)系統(tǒng)中,他是非常長(zhǎng)的2分鐘。這意味著兩分鐘內(nèi),socket對(duì)應(yīng)的地址端口是被占用的,無(wú)法重新綁定。

一個(gè)非?,F(xiàn)實(shí)的問(wèn)題是,假如一個(gè)systemd托管的service異常退出了,留下了TIME_WAIT狀態(tài)的socket,那么systemd將會(huì)嘗試重啟這個(gè)service。但是因?yàn)槎丝诒徽加?,?huì)導(dǎo)致啟動(dòng)失敗,造成兩分鐘的服務(wù)空檔期,systemd也可能在這期間放棄重啟服務(wù)。

但是在設(shè)置了SO_REUSEADDR以后,處于TIME_WAIT狀態(tài)的地址也可以被綁定,就杜絕了這個(gè)問(wèn)題。因?yàn)?code>TIME_WAIT其實(shí)本身就是半死狀態(tài),雖然這樣重用TIME_WAIT可能會(huì)造成不可預(yù)料的副作用,但是在現(xiàn)實(shí)中問(wèn)題很少發(fā)生,所以也忽略了它的副作用。

另外,上文中所有SO_REUSEADDR的生效,只需要在后綁定的socket中設(shè)置SO_REUSEADDR即可,并沒(méi)有強(qiáng)制要求以前綁定的socket也設(shè)置這一個(gè)選項(xiàng)才能完成共享。

SO_REUSEPORT

SO_REUSEPORT干的其實(shí)是大眾期望SO_REUSEADDR能夠干的事,將多個(gè)socket綁定到同一ip和端口。并且它要求所有綁定同一ip/port的socket都設(shè)置了SO_REUSEPORT。不過(guò)可能有的操作系統(tǒng)并沒(méi)有這個(gè)option。

Connect() 返回 EADDRINUSE 問(wèn)題

在默認(rèn)情況下,一般在bind()時(shí)可能會(huì)出現(xiàn)EADDRINUSE問(wèn)題,connect()時(shí)因?yàn)?code>src ip和src port已經(jīng)不同,不可能報(bào)EADDRINUSE。但是在SO_REUSEADDRSO_REUSEPORT下,因?yàn)榈刂酚兄赜茫敲串?dāng)重用的地址端口嘗試連接同一個(gè)遠(yuǎn)端主機(jī)的同一端口時(shí),就會(huì)報(bào)EADDRINUSE。

比如本機(jī)只有兩個(gè)地址,127.0.0.1192.168.0.1,其中后者是可訪問(wèn)因特網(wǎng)的網(wǎng)卡的地址。在SO_REUSEADDR下,并且Socket A綁定了Socket A0.0.0.0:8000, Socket B綁定了192.168.0.1:8000以后,Socket A發(fā)起了與遠(yuǎn)端主機(jī)111.13.101.208:80的連接。此時(shí)根據(jù)路由表規(guī)則,連接將被綁定到192.168.0.1,產(chǎn)生的連接ID為{<SOCK_STREAM>, <192.168.0.1>, <8000>, <111.13.101.208>, <80>},Socket A連接成功。但是如果Socket B也想嘗試發(fā)起與遠(yuǎn)端主機(jī)111.13.101.208:80的連接,就會(huì)產(chǎn)生一樣的連接ID,所以報(bào)了EADDRINUSE。

操作系統(tǒng)的區(qū)別

BSD/mac os

沒(méi)區(qū)別

Linux
Linux < 3.9

在Linux 3.9之前,只存在SO_REUSEADDR配置項(xiàng)。他的主要邏輯與BSD相同,但是存在兩個(gè)意外:

  1. Linux在綁定端口上比BSD更加嚴(yán)格,類似于BSD那種同時(shí)綁定通配地址和特定地址的行為,在linux中不被允許。比如在Linux中已經(jīng)綁定了192.168.0.100:8000,那么即使設(shè)置了SO_REUSEADDR,也將無(wú)法繼續(xù)綁定0.0.0.0:8000。

  2. 另一個(gè)區(qū)別是,在Linux的client socket(也就是不需要顯式綁定端口,不需要listen的socket),如果設(shè)置了SO_REUSEADDR,那么它的作用與BSD中的SO_REUSEPORT完全相同。即Linux允許多個(gè)client socket綁定到同一ip的同一端口。這是為了應(yīng)對(duì)需要綁定多個(gè)socket到udp地址端口以處理不同的protocol的場(chǎng)景。

Linux >= 3.9

Linux 3.9及之后的版本都添加了SO_REUSEPORT選項(xiàng),它的工作原理與BSD基本相同,但是依舊多了兩個(gè)限制:

  1. 為了防止端口挾持,只用屬于同一有效uid的進(jìn)程可以通過(guò)設(shè)置選項(xiàng)來(lái)共享ip及端口。

  2. 對(duì)共享端口的UDP socket而言,內(nèi)核均勻地分發(fā)數(shù)據(jù)報(bào)給每一個(gè)socket。而對(duì)共享端口的TCP socket而言,內(nèi)核將均勻地分發(fā)連接請(qǐng)求(也就是accept()階段)。這個(gè)特性可以用來(lái)作為樸素的負(fù)載均衡。

Windows

Windows中沒(méi)有SO_REUSEPORT選項(xiàng),SO_REUSEADDR承擔(dān)了SO_REUSEPORT的功能。另外,設(shè)置了SO_REUSEADDR的socket總是能綁定到一個(gè)已經(jīng)被占用的ip端口上,即使先來(lái)的socket沒(méi)有設(shè)置SO_REUSEADDR。這是很強(qiáng)的安全風(fēng)險(xiǎn),所以微軟后來(lái)新加了一個(gè)SO_EXCLUSIVEADDRUS的選項(xiàng)來(lái)讓程序顯式地綁定到ip端口上,這樣其他socket即使設(shè)置了SO_REUSEPORT也無(wú)法重用此端口。
Windows提供了詳細(xì)的介紹,詳見Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容