前些天,與另外一個項目組的同僚聊天的時候,談到他遇到的一個有意思的BUG。在window上啟動伺服器,然後用戶端連接配接的時候收到一些奇怪的消息,查證了,原來是他自己的另一個工具也在相同的位址上監聽,用戶端連接配接到了後面這個工具程式上。我問他,是相同的IP和端口?他說是的,因為伺服器代碼和工具程式都設定了SO_REUSEADDR這個socket選項,是以可以在同樣的位址上監聽。
可是,在我的認知裡面, SO_REUSEADDR這個選項并不是說讓兩個程式在相同位址(相同的IP 和 端口)上監聽,而是說可以讓處于time_wait狀态的socket可以快速複用,搜了一下,看到的這篇文章,也是這麼說的:
SO_REUSEADDR allows your server to bind to an address which is in a TIME_WAIT state. It does not allow more than one server to bind to the same address.
看了一下Linux manual,關于這個選項是這麼描述的:
manual并沒有提到time_wait的事情,但是明确指出,如果一個socket處于listen狀态,那麼同樣的端口(port)是不能再次被綁定的(binding),不能binding,自然也不能再次listen,是以是不可能兩個程式在相同的位址(IP PORT)上監聽的。
于是自己用python在寫了一個小的測試程式:
服務端代碼:

tcp_server.py
用戶端代碼:

tcp_client.py
服務端代碼設定了SO_REUSEADDR,在Linux下, 确實不能在相同的位址(IP, Port)上監聽, 但是在windows上,卻又是可以的。于是想到,這個選項可能與平台相關。
網上搜了一下,結果發現了這篇文章《SO_REUSEADDR和SO_REUSEPORT異同》,該文章翻譯自stackoverflow上的這個問答《socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t》,關于SO_REUSEADDR和SO_REUSEPORT這兩個選項在不同平台上的表現介紹得很清楚。不過,中文翻譯水準不怎麼好,像是用機器翻譯的,可以的話還是盡量看原文。
本文記錄一下這個問答的要點,并用上面的小程式在各個平台(Linux, Mac, Windows)上進行測試。注意,本文隻關注TCP、單點傳播,事實上原問答還包括UDP、多點傳播知識,感興趣的讀者可以自行閱讀。
第零:一條tcp連接配接是一個五元祖: {<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
第一:SO_REUSEPORT和SO_REUSEADDR在不同的作業系統上行為是不一樣的
第二:預設情況下,任意兩個socket都無法綁定到相同的源IP位址和源端口, 0.0.0.0 (即INADDR_ANY )和所有其他位址沖突
第三:BSD系統下
SO_REUSEADDR 使得0.0.0.0 與 其他位址不沖突
SO_REUSEPORT允許你将多個socket綁定到相同的位址和端口, 但第一個啟動的socket必須設定SO_REUSEPORT
第四:MacOS IOS 表現同 BSD
第五:Linux
SO_REUSEADDR 隻要有socket處于listen狀态, 就不能在同樣的位址和端口上listen, 0.0.0.0 與其他所有位址沖突
隻要監聽前設定了SO_REUSEPORT(在Linux3.9版本之後可用) ,就可以在相同的(ip port)上監聽
對于SO_REUSEPORT:為了阻止"port 劫持"(Port hijacking)有一個特别的限制,所有希望共享源位址和端口的socket都必須擁有相同的有效使用者id(effective user ID);對于TCP監聽socket,核心嘗試将新的客戶連接配接請求(由accept傳回)平均的交給共享同一位址和端口的socket(監聽socket)
第六:Android同Linux
第七:Windows
隻有SO_REUSEADDR選項,沒有SO_REUSEPORT。
設定SO_REUSEADDR 等價于BSD上設定了SO_REUSEPORT和SO_REUSEADDR,而且不管之前的端口是否設定了SO_REUSEADDR(存疑)
上述選項存在風險:因為允許一個應用程式從别的應用程式上"偷取"已連接配接的端口。是以在windows上加入了另一個socket選項: SO_EXECLUSIVEADDRUSE。設定了SO_EXECLUSIVEADDRUSE的socket確定一旦綁定成功,那麼被綁定的源端口和位址就隻屬于這一個socket,其它的socket不能綁定,甚至他們使用了SO_REUSEADDR也沒用。
在後文涉及到的三個平台(Linux 、MacOS、Windows),都涉及到三個IP:127.0.0.1, 0.0.0.0,10.0.0.x(區域網路IP)。使用的腳本如上(tcp_server.py, tcp_client.py),運作的時候需要簡單修改tcp_server.py中第9、10行的注釋,以便測試不同選項下的效果。
由于沒有BSD系統,而且前文提到MacOS和BSD系統的表現是一樣的,是以在這裡實在MAC上測試
在不使用SO_REUSEADDR (此時未使用SO_REUSEPORT)時:
注意:first指第一條監聽的socket,second指第二條希望在同樣的端口(port)上監聽的連接配接。相容指第二條連接配接可以成功監聽,不相容則指第二條連接配接不能成功監聽。下同
在使用SO_REUSEADDR(此時未使用SO_REUSEPORT)時:
在使用SO_REUSEADDR情況下,如果第一個scoket在0.0.0.0上監聽,第二個scoket在127.0.0.1上監聽。那麼用戶端使用127.0.0.1連接配接的時候會連接配接到第二個socket;使用10.0.0.x則會連接配接到第一個socket
使用SO_REUSEPORT(同時使用了SO_REUSEADDR):
如果兩個socket都在127.0.0.1上監聽,用戶端也通過127.0.0.1去連接配接,那麼用戶端連接配接都會發被第二個socket accept, 筆者并發實驗了幾十次都是這樣, 但并沒有找到明确的官方文檔說明是否是這樣。
從上面兩個測試可以看到,在linux下,是否使用SO_REUSEADDR并不影響兩個socket的監聽
如果兩個socket都在127.0.0.1上監聽,用戶端也通過127.0.0.1去連接配接, 那麼用戶端連接配接會被作業系統分發到兩個socket上,具體如下
用戶端并發10次連接配接: for ((a=1;a<=10;a++)) ; do (python tcp_client.py 127.0.0.1 &); done
第一個socket accept了六次, 第二個socket accept了10次。
前面已經提到,windows下面隻有SO_REUSEADDR選項,但其功能類似bsd系統下的SO_REUSEADDR與SO_REUSEPORT
在不使用SO_REUSEADDR時:
比如都在127.0.0.1 上監聽時,第二個socket會報錯: socket.error: [Errno 10048] 通常每個套接字位址(協定/網絡位址/端口)
使用SO_REUSEADDR時:
此時,如果兩個socket都在127.0.0.1上監聽,用戶端也通過127.0.0.1去連接配接,那麼多次實驗的結果都是第一個socket accept。
在上面提到,windows第一個socket可以不使用SO_REUSEADDR,隻要第二個socket使用了SO_REUSEADDR,就可以在相同的位址(IP:PORT)上監聽。但是我自己試驗了一把,并不成功:socket.error: [Errno 10013]
上面也提到,如果第一個socket使用了SO_EXECLUSIVEADDRUSE選項,那麼第二個連接配接即使使用了SO_REUSEADDR也無濟于事,那麼是否SO_EXECLUSIVEADDRUSE是預設開啟的呢?但是在Python2.7中,socket并沒有這個屬性
查了一下MSDN,有附圖清晰了說明了在window下SO_REUSEADDR與SO_EXECLUSIVEADDRUSE的關系,如下:
但為什麼使用Python的時候 效果不一樣呢,這個就沒細究了
本文測試了一下socket中SO_REUSEADDR與SO_REUSEPORT在各個平台下的差異性,一些結論隻是實驗結果,并沒有查到官方權威定論,如果有差錯,還請指正!
http://www.unixguide.net/network/socketfaq/4.11.shtml
http://man7.org/linux/man-pages/man7/socket.7.html
http://blog.chinaunix.net/uid-28587158-id-4006500.html
https://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t
https://msdn.microsoft.com/en-us/library/windows/desktop/cc150667(v=vs.85).aspx
本文版權歸作者xybaby(博文位址:http://www.cnblogs.com/xybaby/)所有,歡迎轉載和商用,請在文章頁面明顯位置給出原文連結并保留此段聲明,否則保留追究法律責任的權利,其他事項,可留言咨詢。