前言
服務器重啟進程時總會提示端口已經被綁定的報錯,直到重試好幾次才能重啟成功。
這是因為端口尚未完全關閉的情況,這時如果不設置端口重用,則無法完成綁定,因為端口還處於被別的套接口綁定的狀態之中。
SO_REUSEADDR
簡介
- 允許啟動一個監聽服務器並捆綁其眾所周知端口,即使以前建立的將此端口用做他們的本地端口的連接仍存在。這通常是重啟監聽服務器時出現,若不設置此選項,則bind時將出錯。
- 允許在同一端口上啟動同一服務器的多個實例,只要每個實例捆綁一個不同的本地IP地址即可。對於TCP,我們根本不可能啟動捆綁相同IP地址和相同端口號的多個服務器。
- 允許單個進程捆綁同一端口到多個套接口上,只要每個捆綁指定不同的本地IP地址即可。這一般不用於TCP服務器。
- 允許完全重復地捆綁:當一個IP地址和端口綁定到某個套接口上時,還允許此IP地址和端口捆綁到另一個套接口上。一般來說,這個特性僅在支持多播的系統上才有,而且只對UDP套接口而言(TCP不支持多播)。
Python中的用法
- 測試源碼
import socket
serveripaddr = '127.0.0.1'
tcp_listen_addr = (serveripaddr, 12345)
set_reuse_addr = False # True:允許重用,不會報錯.False:默認不支持重用,會報錯
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if set_reuse_addr:
sock1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock1.bind(tcp_listen_addr)
sock1.listen(1)
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if set_reuse_addr:
sock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock2.bind(tcp_listen_addr)
sock2.listen(1)
- 不允許端口重用有如下報錯:
Traceback (most recent call last):
File "C:\test.py", line 17, in <module>
sock2.bind(tcp_listen_addr)
OSError: [WinError 10048] 通常每個套接字地址(協議/網絡地址/端口)只允許使用一次。
golang用法
已經看了go源碼,沒能琢磨出不修改源碼的方案,大家有辦法搞定,記得給我說說額。
我這邊是通過修改go源碼,編譯出來的可執行程序默認就支持端口重用。
- 修改源碼:
\go\src\net\sockopt_windows.go
,按照如下方法加入一段代碼。
func setDefaultSockopts(s syscall.Handle, family, sotype int, ipv6only bool) error {
if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))
}
// 加入代碼,Start
if family == syscall.AF_INET && sotype == syscall.SOCK_STREAM {
syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}
// 加入代碼,End
if (sotype == syscall.SOCK_DGRAM || sotype == syscall.SOCK_RAW) && family != syscall.AF_UNIX && family != syscall.AF_INET6 {
// Allow broadcast.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1))
}
return nil
}
- 修改源碼:
\go\src\net\sockopt_linux.go
,按照如下方法加入一段代碼。
func setDefaultSockopts(s, family, sotype int, ipv6only bool) error {
if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))
}
// 加入代碼,Start
if family == syscall.AF_INET && sotype == syscall.SOCK_STREAM {
syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}
// 加入代碼,End
if (sotype == syscall.SOCK_DGRAM || sotype == syscall.SOCK_RAW) && family != syscall.AF_UNIX {
// Allow broadcast.
return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1))
}
return nil
}
- 修改完源碼,編譯時需要帶上
-a
強制重新編譯所以包,不然還是會使用緩存的.a
文件進行編譯,例如:go build -a test.go
。
其他學習
關於socket有如下5個重要元素,只要其中一個不同,那系統就能區別不同的socket連接。只要5個完全相同,則后面建立綁定的代碼會報報錯。
因此實際項目中,可以由不同進程對同一個端口不同IP進行綁定。例如可以同時綁定“127.0.0.0:12345”和“192.168.1.10:12345”,操作系統知道只是兩個不同的綁定。
SOCKET | 本方IP | 本方Port | 目的IP | 目的Port | 協議 |
---|---|---|---|---|---|
sokcet1 | 127.0.0.1 | 8000 | 192.168.1.1 | 9000 | Tcp |
socket2 | 127.0.0.1 | 8000 | 192.168.1.1 | 10000 | Tcp |
總結
運用端口重用對於我來說最大的方便就是重啟進程快了很多,不用一遍遍嘗試綁定端口,都不知道啥時候可以成功。
還有就是通過學習,認識到建立監聽的5個元素,只要其中一個不同,就能實例化多個socket連接。