Yes! I think that your analysis is correct. The listener inside the if is not the same as the one outside. You can check using fmt.Println(&listener) and you will see two different addresses.
-- Jacques Le vendredi 6 septembre 2019 15:52:11 UTC+2, jgr...@ou.edu a écrit : > > Ahh, I think I understand the problem! > > I assumed that "listener, err := ..." would use the existing listener > variable > (creating err on the spot), rather than creating a new locally-scoped > listener? > > Therefore, as you said, listener is basically always nil outside that "if > useListener>0 {}" scope? So server.ListenAndServe() is always invoked? > > If so, then - thanks a lot for the help! :) > > Cheers, > > J. > > On Friday, September 6, 2019 at 8:09:37 AM UTC-5, Jacques Supcik wrote: >> >> Hello, >> >> I tried your program and when you call listener, err := >> net.Listen("tcp4", addrString) it does indeed only listen to "tcp4". >> Later, when you call server.ListenAndServe() (because listener is nil), >> the system seems to "re-listen" from the same port and then it listen to >> "tcp" and not only "tcp4" ( >> https://github.com/golang/go/blob/master/src/net/http/server.go#L2826) >> >> I don't understand why you call both "net.Listen" and >> "server.ListenAndServe". If you want an HTTP server, I think that you can >> just use "server.ListenAndServe" and you don't need anything more. If you >> want an HTTP server and a generic TCP server, then you need 2 different >> ports, one for the HTTP server and another for the generic TCP server. But >> perhaps I did not understand your problem ;-) >> >> -- Jacques >> >> >> Le jeudi 5 septembre 2019 22:13:31 UTC+2, jgr...@ou.edu a écrit : >>> >>> Hi! >>> >>> I'm having some confusion over the behaviour of net.Listen() and it's >>> interactions with http.Server. >>> >>> Can anyone take a look at this, and let me know what I'm doing wrong? >>> >>> Thanks! >>> >>> >>> System description >>> ------------------------- >>> >>> Go: go version go1.12.9 darwin/amd64 >>> >>> OS: macOS Mojave (10.14.6) >>> >>> >>> Problem description >>> -------------------------- >>> >>> Passing a net.Listener from net.Listen() into the Serve() method of an >>> http.Server does not behave how I expect ... >>> >>> >>> Test program >>> ----------------- >>> >>> A simple server that responds to connections by echoing info regarding >>> the URL by which it was contacted (see below): >>> >>> package main >>> >>> import ( >>> "context" >>> "flag" >>> "fmt" >>> "log" >>> "net" >>> "net/http" >>> "os" >>> "os/signal" >>> "strconv" >>> "syscall" >>> "time" >>> ) >>> >>> // Print information about the local machine's network interfaces >>> func printNetworkInterfaces() { >>> ifaces, err := net.Interfaces() >>> if err != nil { panic("net.Interfaces()") } >>> >>> if len(ifaces)<1 { >>> log.Println("No network interfaces found.") >>> return >>> } >>> >>> hostname, _ := os.Hostname() >>> log.Println( "Network interfaces for " + hostname ) >>> >>> for _, iface := range ifaces { >>> addrs, err := iface.Addrs() >>> if err != nil { panic("iface.Addrs()") } >>> if len(addrs) < 1 { continue } >>> log.Println("-",iface.Name,iface.HardwareAddr) >>> for _, addr := range addrs { >>> switch v := addr.(type) { >>> case *net.IPNet: >>> str := fmt.Sprintf("IPNet: IP=%s, mask=%s, network=%s, string=%s", v.IP, >>> v.Mask, v.Network(), v.String()) >>> log.Println(" ", str) >>> case *net.IPAddr: >>> str := fmt.Sprintf("IPAddr: IP=%s, zone=%s, network=%s, string=%s", >>> v.IP, v.Zone, v.Network(), v.String()) >>> log.Println(" ", str) >>> default: >>> log.Println("<unknown>") >>> } >>> } >>> } >>> } >>> >>> // Just write the incoming url back to the sender >>> func echoHandler(w http.ResponseWriter, r *http.Request) { >>> txt := fmt.Sprintf("Echo: (%s)",r.URL.Path) >>> w.Write( []byte(txt+"\n") ) >>> log.Println(txt) >>> } >>> >>> var ( >>> listener_ = flag.Int("listener", 0, "Use an explicit net.Listener.") >>> port_ = flag.Int("port", 0, "Set the port to listen on (0 = any free >>> port?).") >>> timeout_ = flag.Int("wait", 0, "Timout (in seconds) before server >>> killed (0 = no timout).") >>> ) >>> >>> func main() { >>> >>> onShutdown := func(what string, cleanup func()) { >>> log.Println( fmt.Sprintf("- Shutting down %s ...",what) ) >>> cleanup() >>> log.Println( fmt.Sprintf(" %s shut down.",what) ) >>> } >>> >>> flag.Parse() >>> >>> useListener := *listener_ >>> port := *port_ >>> timeout := *timeout_ >>> >>> // Let's see what interfaces are present on the local machine >>> >>> printNetworkInterfaces() >>> >>> // Simple server for incoming connections. >>> >>> http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) >>> {echoHandler(w,r)}); >>> >>> var listener net.Listener = nil >>> addrString := fmt.Sprintf(":%d",port) >>> >>> // Using an explicit Listener provides more control over the specifics, >>> // e.g. tcp4/6 and letting the system select a currently free port. >>> >>> if useListener>0 { >>> log.Println("Using net.Listener") >>> >>> listener, err := net.Listen("tcp4", addrString) // :0 -> use any free >>> port >>> if err != nil { log.Fatalln(err) } >>> >>> defer onShutdown("listener", func() {listener.Close()} ) >>> >>> addrString = listener.Addr().String() >>> host, portStr, err := net.SplitHostPort(addrString) // as port may have >>> been assigned by system >>> if err != nil { log.Fatalln(err) } >>> >>> log.Println( fmt.Sprintf("Listener Addr string: %s (host: %s, port: >>> %s)",addrString,host,portStr) ) >>> >>> port, err = strconv.Atoi(portStr) >>> if err != nil { log.Fatalln(err) } >>> >>> addrString = fmt.Sprintf(":%d",port) // as port may have been assigned >>> by the system >>> } >>> >>> server := http.Server { Addr: addrString } >>> >>> // Run web server in a separate goroutine so it doesn't block our >>> progress >>> >>> go func(server *http.Server, listener net.Listener) { >>> >>> var err error >>> >>> if listener == nil { >>> err = server.ListenAndServe() >>> } else { >>> err = server.Serve(listener) >>> } >>> >>> switch err { >>> case nil: >>> case http.ErrServerClosed: >>> log.Println("Caught ErrServerClosed") >>> default: >>> panic(err) >>> } >>> }(&server, listener) >>> >>> defer onShutdown("server", func() >>> {server.Shutdown(context.Background())} ) >>> >>> log.Println("Port:", port) >>> log.Println("Address:", addrString) >>> >>> // User interrupt channel >>> >>> sig := make(chan os.Signal, 1) >>> signal.Notify(sig, os.Interrupt, syscall.SIGTERM) >>> >>> // Timeout channel, if needed >>> >>> tc := make(<-chan time.Time); >>> if timeout > 0 { >>> tc = time.After(time.Second * time.Duration(timeout)) >>> } >>> >>> // Wait on user interrupt or timeout >>> >>> select { >>> case <-sig: // user interrupt >>> case <-tc: // timeout >>> } >>> >>> // Cleanup >>> >>> log.Println("Shutting down.") >>> } >>> >>> According to the Go docs <https://golang.org/pkg/net/#Listen>: >>> >>> For TCP networks, if the host in the address parameter is empty or a >>> literal unspecified IP address, Listen listens on all available unicast and >>> anycast IP addresses of the local system. To only use IPv4, use network >>> "tcp4". >>> >>> It also says "See func Dial for a description of the network and address >>> parameters", from which <https://golang.org/pkg/net/#Dial>: >>> >>> Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", >>> "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" >>> (IPv6-only), "unix", "unixgram" and "unixpacket". >>> >>> Therefore, I would expect that calling net.Listen("tcp4",":0") will >>> listen on an (arbitrary) free port using all IPv4 interfaces. However, >>> lsof indicates that it's listening for both IPv4 and IPv6 (I've masked >>> some identifying information): >>> >>> me$ go run . --listener=1 >>> >>> 2019/09/05 11:14:44 Network interfaces for XXXX >>> 2019/09/05 11:14:44 - lo0 >>> 2019/09/05 11:14:44 IPNet: IP=127.0.0.1, mask=ff000000, >>> network=ip+net, string=127.0.0.1/8 >>> 2019/09/05 11:14:44 IPNet: IP=::1, >>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128 >>> 2019/09/05 11:14:44 IPNet: IP=fe80::1, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64 >>> 2019/09/05 11:14:44 - en0 x:x:x:x:x:x >>> 2019/09/05 11:14:44 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:14:44 IPNet: IP=10.195.66.129, mask=fffff800, >>> network=ip+net, string=10.195.66.129/21 >>> 2019/09/05 11:14:44 - utun0 >>> 2019/09/05 11:14:44 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:18:37 Using net.Listener >>> 2019/09/05 11:18:37 Listener Addr string: 0.0.0.0:53133 (host: 0.0.0.0, >>> port: 53133) >>> 2019/09/05 11:14:44 Port: 53133 >>> 2019/09/05 11:14:44 Address: :53133 >>> >>> me, in another Terminal window$ lsof -i | grep LISTEN >>> >>> ARDAgent 360 me 9u IPv6 0xd5f6fecc3cdf4c41 0t0 TCP >>> *:net-assistant (LISTEN) >>> LogiVCCor 935 me 9u IPv4 0xd5f6fecc47403101 0t0 TCP *:iims >>> (LISTEN) >>> LogiVCCor 935 me 14u IPv6 0xd5f6fecc3cdf40c1 0t0 TCP *:iims >>> (LISTEN) >>> go_listen 14859 me 3u IPv4 0xd5f6fecc451a7781 0t0 TCP *:53133 >>> (LISTEN) >>> go_listen 14859 me 5u IPv6 0xd5f6fecc3cdf4681 0t0 TCP *:53133 >>> (LISTEN) >>> >>> Furthermore, it's only responding on localhost and [::1]; using any of >>> the other interface addresses listed by net.Interfaces() fails to get a >>> response: >>> >>> me$ time curl localhost:53133/localhost >>> Echo: (/localhost) >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [::1]:53133/[::1] >>> Echo: (/[::1]) >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.005s >>> >>> me$ time curl 127.0.0.1:53133/127.0.0.1 >>> ^C >>> >>> real 0m4.521s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [fe80::1]:53133/[fe80::1] >>> curl: (7) Couldn't connect to server >>> >>> real 0m0.014s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl 10.195.66.129:53133/10.195.66.129 >>> ^C >>> >>> real 0m6.465s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [fe80::x:x:x:x]:53133/[fe80::x:x:x:x] >>> curl: (7) Couldn't connect to server >>> >>> real 0m0.013s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> It looks like trying to connect via (non-localhost) IPv4 addresses >>> hangs on both lo0 and en0 (127.0.0.1, 10.195.66.129), and (non-[::1]) >>> IPv6 addresses flat out refuse to connect. >>> >>> However, if we skip the use of a net.Listener, it looks like the >>> http.Server only listens for IPv6: >>> >>> me$ go run . >>> 2019/09/05 11:26:38 Network interfaces for XXXX >>> 2019/09/05 11:26:38 - lo0 >>> 2019/09/05 11:26:38 IPNet: IP=127.0.0.1, mask=ff000000, >>> network=ip+net, string=127.0.0.1/8 >>> 2019/09/05 11:26:38 IPNet: IP=::1, >>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128 >>> 2019/09/05 11:26:38 IPNet: IP=fe80::1, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64 >>> 2019/09/05 11:26:38 - en0 x:x:x:x:x:x >>> 2019/09/05 11:26:38 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:26:38 IPNet: IP=10.195.66.129, mask=fffff800, >>> network=ip+net, string=10.195.66.129/21 >>> 2019/09/05 11:26:38 - utun0 >>> 2019/09/05 11:26:38 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:26:38 Port: 0 >>> 2019/09/05 11:26:38 Address: :0 >>> >>> me, in another Terminal window$ lsof -i | grep LISTEN >>> ARDAgent 360 me 9u IPv6 0xd5f6fecc3cdf4c41 0t0 TCP >>> *:net-assistant (LISTEN) >>> LogiVCCor 935 me 9u IPv4 0xd5f6fecc47403101 0t0 TCP *:iims >>> (LISTEN) >>> LogiVCCor 935 me 14u IPv6 0xd5f6fecc3cdf40c1 0t0 TCP *:iims >>> (LISTEN) >>> go_listen 15016 me 3u IPv6 0xd5f6fecc3cdf5201 0t0 TCP *:53170 >>> (LISTEN) >>> >>> This approach (i.e., ignoring net.Listener to simply use >>> server.ListenAndServe()) seems to do a better job of listening on >>> multiple interfaces/addresses: >>> >>> me$ time curl localhost:53170/localhost >>> Echo: (/localhost) >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [::1]:53170/[::1] >>> Echo: (/[::1]) >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl 127.0.0.1:53170/127.0.0.1 >>> Echo: (/127.0.0.1) >>> >>> real 0m0.014s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [fe80::1]:53170/[fe80::1] >>> curl: (7) Couldn't connect to server >>> >>> real 0m0.014s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl 10.195.66.129:53170/10.195.66.129 >>> Echo: (/10.195.66.129) >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> me$ time curl [fe80::x:x:x:x]:53170/[fe80::x:x:x:x] >>> curl: (7) Couldn't connect to server >>> >>> real 0m0.015s >>> user 0m0.004s >>> sys 0m0.004s >>> >>> ... although it also has problems for IPv6 addresses other than ::1. >>> >>> If I use tcp instead of tcp4 in the call to net.Listen() (e.g. >>> net.Listen("tcp", >>> ":0")) I *always* get a bind error due to address already in use: >>> >>> me$ go run . --listener=1 >>> 2019/09/05 11:45:13 Network interfaces for XXXX >>> 2019/09/05 11:45:13 - lo0 >>> 2019/09/05 11:45:13 IPNet: IP=127.0.0.1, mask=ff000000, >>> network=ip+net, string=127.0.0.1/8 >>> 2019/09/05 11:45:13 IPNet: IP=::1, >>> mask=ffffffffffffffffffffffffffffffff, network=ip+net, string=::1/128 >>> 2019/09/05 11:45:13 IPNet: IP=fe80::1, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, string=fe80::1/64 >>> 2019/09/05 11:45:13 - en0 x:x:x:x:x:x >>> 2019/09/05 11:45:13 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:45:13 IPNet: IP=10.195.66.129, mask=fffff800, >>> network=ip+net, string=10.195.66.129/21 >>> 2019/09/05 11:45:13 - utun0 >>> 2019/09/05 11:45:13 IPNet: IP=fe80::x:x:x:x, >>> mask=ffffffffffffffff0000000000000000, network=ip+net, >>> string=fe80::x:x:x:x/64 >>> 2019/09/05 11:45:13 Using net.Listener >>> 2019/09/05 11:45:13 Listener Addr string: [::]:53260 (host: ::, port: >>> 53260) >>> 2019/09/05 11:45:13 Port: 53260 >>> 2019/09/05 11:45:13 Address: :53260 >>> panic: listen tcp :53260: bind: address already in use >>> >>> I get the same outcome if I use tcp6 in net.Listen(); I can only get >>> the program to run using tcp4 which, on my machine at least, actually >>> seems to open an additional IPv6 connection anyway - so I'm confused as to >>> why net.Listen() doesn't seem to like tcp6. >>> >>> Despite the net.Listen() documentation directing you to net.Dial() docs >>> for a discussion of the network parameter, some of these networks (e.g. >>> ip, ip4, ip6) are unknown to net.Listen(). The documentation could be >>> clearer in that respect! :) >>> >>> I can't figure out what's going on here. Any ideas what I might be doing >>> wrong? >>> >>> -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/235ccc9c-6a94-4325-8fcd-9b6b80dd2f8a%40googlegroups.com.