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.

Reply via email to