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/662260b6-a800-4996-b2de-e61f2f8e8e40%40googlegroups.com.