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/497abdc2-2f85-45b8-9442-d51d5cf5b0e9%40googlegroups.com.