├── .gitattributes ├── go.mod ├── .gitignore ├── greuse_other.go ├── greuse_windows.go ├── greuse_addr.go ├── .example └── example.go ├── greuse_unix.go ├── LICENSE ├── README.MD ├── greuse.go └── greuse_z_unit_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=GO 2 | *.css linguist-language=GO 3 | *.html linguist-language=GO -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gogf/greuse 2 | 3 | go 1.8 4 | 5 | require golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .hgignore.swp 3 | .project 4 | .orig 5 | .swp 6 | .idea/ 7 | .settings/ 8 | .vscode/ 9 | vender/ 10 | log/ 11 | composer.lock 12 | gitpush.sh 13 | pkg/ 14 | bin/ 15 | cbuild 16 | **/.DS_Store 17 | .vscode/ 18 | go.sum 19 | 20 | -------------------------------------------------------------------------------- /greuse_other.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd 2 | 3 | package greuse 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | // See net.RawConn.Control 10 | func Control(network, address string, c syscall.RawConn) (err error) { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /greuse_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package greuse 4 | 5 | import ( 6 | "golang.org/x/sys/windows" 7 | "syscall" 8 | ) 9 | 10 | // See net.RawConn.Control 11 | func Control(network, address string, c syscall.RawConn) (err error) { 12 | e := c.Control(func(fd uintptr) { 13 | if err = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1); err != nil { 14 | return 15 | } 16 | }) 17 | if e != nil { 18 | return e 19 | } 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /greuse_addr.go: -------------------------------------------------------------------------------- 1 | package greuse 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // ResolveAddr parses given parameters to net.Addr. 8 | func ResolveAddr(network, address string) (net.Addr, error) { 9 | switch network { 10 | case "ip", "ip4", "ip6": 11 | return net.ResolveIPAddr(network, address) 12 | case "tcp", "tcp4", "tcp6": 13 | return net.ResolveTCPAddr(network, address) 14 | case "udp", "udp4", "udp6": 15 | return net.ResolveUDPAddr(network, address) 16 | case "unix", "unixgram", "unixpacket": 17 | return net.ResolveUnixAddr(network, address) 18 | default: 19 | return nil, net.UnknownNetworkError(network) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gogf/greuse" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | // We can create two processes with this code. 11 | // Do some requests, then see the response from the server. 12 | func main() { 13 | listener, err := greuse.Listen("tcp", ":8881") 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer listener.Close() 18 | 19 | server := &http.Server{} 20 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 21 | fmt.Fprintf(w, "gid: %d, pid: %d\n", os.Getgid(), os.Getpid()) 22 | }) 23 | 24 | panic(server.Serve(listener)) 25 | } 26 | -------------------------------------------------------------------------------- /greuse_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin dragonfly freebsd netbsd openbsd 2 | 3 | package greuse 4 | 5 | import ( 6 | "golang.org/x/sys/unix" 7 | "syscall" 8 | ) 9 | 10 | func init() { 11 | Enabled = true 12 | } 13 | 14 | // See net.RawConn.Control 15 | func Control(network, address string, c syscall.RawConn) (err error) { 16 | e := c.Control(func(fd uintptr) { 17 | // SO_REUSEADDR 18 | if err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { 19 | panic(err) 20 | } 21 | // SO_REUSEPORT 22 | if err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { 23 | panic(err) 24 | } 25 | }) 26 | if e != nil { 27 | return e 28 | } 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 john@goframe.org https://goframe.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # greuse 2 | 3 | Package greuse provides Listen and Dial functions that set socket options in order to be able to reuse ports. 4 | You should only use this package if you know what SO_REUSEADDR and SO_REUSEPORT are. 5 | 6 | # Installation 7 | ``` 8 | go get -u -v github.com/gogf/greuse 9 | ``` 10 | or use `go.mod` 11 | ``` 12 | require github.com/gogf/greuse latest 13 | ``` 14 | 15 | # Documentation 16 | 17 | * [https://godoc.org/github.com/gogf/greuse](https://godoc.org/github.com/gogf/greuse) 18 | 19 | # Quick Start 20 | ```go 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "github.com/gogf/greuse" 26 | "net/http" 27 | "os" 28 | ) 29 | 30 | // We can create two processes with this code. 31 | // Do some requests, then watch the output of the console. 32 | func main() { 33 | listener, err := greuse.Listen("tcp", ":8881") 34 | if err != nil { 35 | panic(err) 36 | } 37 | defer listener.Close() 38 | 39 | server := &http.Server{} 40 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 41 | fmt.Fprintf(w, "gid: %d, pid: %d\n", os.Getgid(), os.Getpid()) 42 | }) 43 | 44 | panic(server.Serve(listener)) 45 | } 46 | ``` 47 | 48 | 49 | # License 50 | 51 | `greuse` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever. 52 | 53 | -------------------------------------------------------------------------------- /greuse.go: -------------------------------------------------------------------------------- 1 | // Package greuse provides Listen and Dial functions that set socket 2 | // options in order to be able to reuse ports. You should only use this 3 | // package if you know what SO_REUSEADDR and SO_REUSEPORT are. 4 | // 5 | // For example: 6 | // 7 | // // listen on the same port. 8 | // l1, _ := greuse.Listen("tcp", "127.0.0.1:1234") 9 | // l2, _ := greuse.Listen("tcp", "127.0.0.1:1234") 10 | // 11 | // // dial from the same port. 12 | // l1, _ := greuse.Listen("tcp", "127.0.0.1:1234") 13 | // l2, _ := greuse.Listen("tcp", "127.0.0.1:1235") 14 | // c, _ := greuse.Dial("tcp", "127.0.0.1:1234", "127.0.0.1:1235") 15 | // 16 | // Note: can't dial self because tcp/ip stacks use 4-tuples to identify connections, 17 | // and doing so would clash. 18 | package greuse 19 | 20 | import ( 21 | "context" 22 | "net" 23 | ) 24 | 25 | var ( 26 | Enabled = false 27 | listenConfig = net.ListenConfig{ 28 | Control: Control, 29 | } 30 | ) 31 | 32 | // Listen listens at the given network and address. see net.Listen 33 | // Returns a net.Listener created from a file discriptor for a socket 34 | // with SO_REUSEPORT and SO_REUSEADDR option set. 35 | func Listen(network, address string) (net.Listener, error) { 36 | return listenConfig.Listen(context.Background(), network, address) 37 | } 38 | 39 | // ListenPacket listens at the given network and address. see net.ListenPacket 40 | // Returns a net.Listener created from a file discriptor for a socket 41 | // with SO_REUSEPORT and SO_REUSEADDR option set. 42 | func ListenPacket(network, address string) (net.PacketConn, error) { 43 | return listenConfig.ListenPacket(context.Background(), network, address) 44 | } 45 | 46 | // Dial dials the given network and address. see net.Dialer.Dial 47 | // Returns a net.Conn created from a file discriptor for a socket 48 | // with SO_REUSEPORT and SO_REUSEADDR option set. 49 | func Dial(network, laddr, raddr string) (net.Conn, error) { 50 | nla, err := ResolveAddr(network, laddr) 51 | if err != nil { 52 | return nil, err 53 | } 54 | d := net.Dialer{ 55 | Control: Control, 56 | LocalAddr: nla, 57 | } 58 | return d.Dial(network, raddr) 59 | } 60 | -------------------------------------------------------------------------------- /greuse_z_unit_test.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin dragonfly freebsd netbsd openbsd 2 | 3 | package greuse_test 4 | 5 | import ( 6 | "fmt" 7 | "github.com/gogf/greuse" 8 | "html" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "os" 13 | "testing" 14 | ) 15 | 16 | const ( 17 | httpServerOneResponse = "1" 18 | httpServerTwoResponse = "2" 19 | ) 20 | 21 | var ( 22 | httpServerOne = NewHTTPServer(httpServerOneResponse) 23 | httpServerTwo = NewHTTPServer(httpServerTwoResponse) 24 | ) 25 | 26 | func NewHTTPServer(resp string) *httptest.Server { 27 | return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 28 | fmt.Fprint(w, resp) 29 | })) 30 | } 31 | func TestNewReusablePortListener(t *testing.T) { 32 | listenerOne, err := greuse.Listen("tcp4", "localhost:10081") 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | defer listenerOne.Close() 37 | 38 | listenerTwo, err := greuse.Listen("tcp", "127.0.0.1:10081") 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | defer listenerTwo.Close() 43 | 44 | listenerThree, err := greuse.Listen("tcp6", "[::]:10081") 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | defer listenerThree.Close() 49 | 50 | listenerFour, err := greuse.Listen("tcp6", ":10081") 51 | if err != nil { 52 | t.Error(err) 53 | } 54 | defer listenerFour.Close() 55 | 56 | listenerFive, err := greuse.Listen("tcp4", ":10081") 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | defer listenerFive.Close() 61 | 62 | listenerSix, err := greuse.Listen("tcp", ":10081") 63 | if err != nil { 64 | t.Error(err) 65 | } 66 | defer listenerSix.Close() 67 | } 68 | 69 | func TestListen(t *testing.T) { 70 | listenerOne, err := greuse.Listen("tcp4", "localhost:10081") 71 | if err != nil { 72 | t.Error(err) 73 | } 74 | defer listenerOne.Close() 75 | 76 | listenerTwo, err := greuse.Listen("tcp", "127.0.0.1:10081") 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | defer listenerTwo.Close() 81 | 82 | listenerThree, err := greuse.Listen("tcp6", "[::]:10081") 83 | if err != nil { 84 | t.Error(err) 85 | } 86 | defer listenerThree.Close() 87 | 88 | listenerFour, err := greuse.Listen("tcp6", ":10081") 89 | if err != nil { 90 | t.Error(err) 91 | } 92 | defer listenerFour.Close() 93 | 94 | listenerFive, err := greuse.Listen("tcp4", ":10081") 95 | if err != nil { 96 | t.Error(err) 97 | } 98 | defer listenerFive.Close() 99 | 100 | listenerSix, err := greuse.Listen("tcp", ":10081") 101 | if err != nil { 102 | t.Error(err) 103 | } 104 | defer listenerSix.Close() 105 | } 106 | 107 | func TestNewReusablePortServers(t *testing.T) { 108 | listenerOne, err := greuse.Listen("tcp4", "localhost:10081") 109 | if err != nil { 110 | t.Error(err) 111 | } 112 | defer listenerOne.Close() 113 | 114 | listenerTwo, err := greuse.Listen("tcp6", ":10081") 115 | if err != nil { 116 | t.Error(err) 117 | } 118 | defer listenerTwo.Close() 119 | 120 | httpServerOne.Listener = listenerOne 121 | httpServerTwo.Listener = listenerTwo 122 | 123 | httpServerOne.Start() 124 | httpServerTwo.Start() 125 | 126 | // Server One — First Response 127 | resp1, err := http.Get(httpServerOne.URL) 128 | if err != nil { 129 | t.Error(err) 130 | } 131 | body1, err := ioutil.ReadAll(resp1.Body) 132 | resp1.Body.Close() 133 | if err != nil { 134 | t.Error(err) 135 | } 136 | if string(body1) != httpServerOneResponse && string(body1) != httpServerTwoResponse { 137 | t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body1)) 138 | } 139 | 140 | // Server Two — First Response 141 | resp2, err := http.Get(httpServerTwo.URL) 142 | if err != nil { 143 | t.Error(err) 144 | } 145 | body2, err := ioutil.ReadAll(resp2.Body) 146 | resp1.Body.Close() 147 | if err != nil { 148 | t.Error(err) 149 | } 150 | if string(body2) != httpServerOneResponse && string(body2) != httpServerTwoResponse { 151 | t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body2)) 152 | } 153 | 154 | httpServerTwo.Close() 155 | 156 | // Server One — Second Response 157 | resp3, err := http.Get(httpServerOne.URL) 158 | if err != nil { 159 | t.Error(err) 160 | } 161 | body3, err := ioutil.ReadAll(resp3.Body) 162 | resp1.Body.Close() 163 | if err != nil { 164 | t.Error(err) 165 | } 166 | if string(body3) != httpServerOneResponse { 167 | t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body3)) 168 | } 169 | 170 | // Server One — Third Response 171 | resp5, err := http.Get(httpServerOne.URL) 172 | if err != nil { 173 | t.Error(err) 174 | } 175 | body5, err := ioutil.ReadAll(resp5.Body) 176 | resp1.Body.Close() 177 | if err != nil { 178 | t.Error(err) 179 | } 180 | if string(body5) != httpServerOneResponse { 181 | t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body5)) 182 | } 183 | 184 | httpServerOne.Close() 185 | } 186 | 187 | func BenchmarkNewReusablePortListener(b *testing.B) { 188 | for i := 0; i < b.N; i++ { 189 | listener, err := greuse.Listen("tcp", ":10081") 190 | 191 | if err != nil { 192 | b.Error(err) 193 | } else { 194 | listener.Close() 195 | } 196 | } 197 | } 198 | 199 | func ExampleNewReusablePortListener() { 200 | listener, err := greuse.Listen("tcp", ":8881") 201 | if err != nil { 202 | panic(err) 203 | } 204 | defer listener.Close() 205 | 206 | server := &http.Server{} 207 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 208 | fmt.Println(os.Getgid()) 209 | fmt.Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path)) 210 | }) 211 | 212 | panic(server.Serve(listener)) 213 | } 214 | --------------------------------------------------------------------------------