SO_REUSEADDR與SO_REUSEPORT平台差異性與測試


  前些天,與另外一個項目組的同事聊天的時候,談到他遇到的一個有意思的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,關於這個選項是這么描述的:

SO_REUSEADDR
              Indicates that the rules used in validating addresses supplied
              in a bind(2) call should allow reuse of local addresses.  For
              AF_INET sockets this means that a socket may bind, except when
              there is an active listening socket bound to the address.
              When the listening socket is bound to INADDR_ANY with a
              specific port then it is not possible to bind to this port for
              any local address.  Argument is an integer boolean flag.

  manual並沒有提到time_wait的事情,但是明確指出,如果一個socket處於listen狀態,那么同樣的端口(port)是不能再次被綁定的(binding),不能binding,自然也不能再次listen,因此是不可能兩個程序在相同的地址(IP PORT)上監聽的。

  於是自己用python在寫了一個小的測試程序:

  服務端代碼:

 1 # -*- coding: utf-8 -*-
 2 import socket, sys
 3 import time
 4 
 5 def main():
 6     HOST, PORT = sys.argv[1], 8888
 7 
 8     listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 9     listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
10     # print listen_socket.getsockopt(socket.SOL_SOCKET, socket.SO_EXECLUSIVEADDRUS)
11 
12     listen_socket.bind((HOST, PORT))
13     listen_socket.listen(10)
14 
15     print 'Serving on host %s port %s ...' %(HOST, PORT)
16     while True:
17         client_connection, client_address = listen_socket.accept()
18         request = client_connection.recv(1024)
19         print 'client ', request
20 
21         for i in range(5):
22             http_response = """\
23             hello
24             """
25             client_connection.sendall(http_response)
26             time.sleep(3)
27         client_connection.close()
28 
29 if __name__ == '__main__':
30     main()
tcp_server.py

  客戶端代碼:

 1 import socket, sys
 2 
 3 def main():
 4     server_address = ("localhost" if len(sys.argv) == 1 else sys.argv[1],8888)
 5     s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 6     s.connect(server_address)
 7     print s.getpeername()
 8     s.send('I AM CLIENT')
 9     while True:
10         data = s.recv(1024)
11         print " %s received %s" % (s.getpeername(),data)
12         if not data:
13             print "closing socket ",s.getpeername()
14             s.close()
15 
16 if __name__ == '__main__':
17     main()
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行的注釋,以便測試不同選項下的效果。

MAC

  由於沒有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 (此時未使用SO_REUSEPORT)時:
  

 

  在使用SO_REUSEADDR(此時未使用SO_REUSEPORT)時:
  

  從上面兩個測試可以看到,在linux下,是否使用SO_REUSEADDR並不影響兩個socket的監聽

 

  使用SO_REUSEPORT(同時使用了SO_REUSEADDR):

  

  如果兩個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

  前面已經提到,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在各個平台下的差異性,一些結論只是實驗結果,並沒有查到官方權威定論,如果有差錯,還請指正!

references

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM