Golang net.Listen绑定到已使用的端口

lli*_*lib 5 python sockets macos networking go

端口8888已通过在docker容器中运行的进程绑定到我的(OS X 10.13.5)系统上:

$ netstat -an | grep 8888
tcp6       0      0  ::1.8888               *.*                    LISTEN
tcp4       0      0  *.8888                 *.*                    LISTEN
Run Code Online (Sandbox Code Playgroud)

尝试绑定到该端口的python程序(使用我可以管理的与golang尽可能接近的socket选项),以我期望的方式失败:

$ netstat -an | grep 8888
tcp6       0      0  ::1.8888               *.*                    LISTEN
tcp4       0      0  *.8888                 *.*                    LISTEN
Run Code Online (Sandbox Code Playgroud)

失败:

$ python test.py
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    main()
  File "test.py", line 11, in main
    sock.bind(("0.0.0.0", 8888))
OSError: [Errno 48] Address already in use
Run Code Online (Sandbox Code Playgroud)

但是go程序通过创建连接net.Listen不会失败,正如我期望的那样:

package main

import (
    "fmt"
    "net"
)

func main() {
    _, err := net.Listen("tcp", "0.0.0.0:8888")
    if err != nil {
        fmt.Printf("Connection error: %s\n", err)
    } else {
        fmt.Println("Listening")
    }
}
Run Code Online (Sandbox Code Playgroud)

成功与:

$ go run test.go
Listening
Run Code Online (Sandbox Code Playgroud)

一位同事报告说,使用相同的设置,他的Ubuntu系统正确地使go程序失败。

为什么在Mac上成功完成此操作,如何获得net.Listen来显示绑定到端口8888的错误?

编辑:如果我使用简单的go程序占用端口8888,例如:

package main

import (
    "log"
    "net/http"
)

func main() {
    log.Fatal(http.ListenAndServe("0.0.0.0:8888", nil))
}
Run Code Online (Sandbox Code Playgroud)

然后test.go正确无法绑定到端口。但是docker进程(基本上在运行^^^)不会导致它失败。

编辑2:如果我指定“ tcp4”,则该程序确实确实失败,正如我期望的那样。如果我指定“ tcp6”,它会成功,但是netstat表示它绑定到*而不是::1

$ netstat -an | grep 8888
tcp6       0      0  *.8888                 *.*                    LISTEN
tcp6       0      0  ::1.8888               *.*                    LISTEN
tcp4       0      0  *.8888                 *.*                    LISTEN
Run Code Online (Sandbox Code Playgroud)

因此,指定“ tcp4”将解决我的实际问题,但是我真的很想了解“ tcp46”连接类型到底是怎么回事,而我找不到任何文档。救命!

lli*_*lib 5

好吧,我想我有一个故事可以讲述为什么会发生这种情况:

  1. Mac 上的 Docker 在映射端口时会绑定 IPv40.0.0.0:<port>和 IPv6 [::1]:<port>。请注意,在 IPv6 上,它映射到等效项localhost而不是0.0.0.0,这将是[::]
  2. Golang 在打开套接字进行侦听时,默认情况下会打开一个以某种方式映射的 IPv6 套接字,以侦听 IPv4。(我仍然不完全理解这种tcp46类型,所以如果你有好的文档,给我指出!)。
  3. 所以我的 golang 程序在 上打开一个 IPv6 套接字[::]:8888,相当于0.0.0.0:8888在 IPv6 中。这是成功的,因为 docker 正在侦听[::1](相当于 127.0.0.1),而不是 [::]
  4. 就是这样!因此,golang 程序成功了,即使它只能通过从非环回地址连接到 IPv6 的客户端进行连接(我想,我太累了,无法测试这个,让我休息一下)

我的同事报告说,在 Ubuntu 上,docker 监听[::],这就是为什么他无法重现我所看到的问题。这似乎是明智的行为!我不知道为什么它在 mac 上不这样做。

我还认为 Go 在这种情况下很高兴地成功了,这令人惊讶,而且可能有点错误,尽管它创建了一个很难实际访问的套接字?但我不能说这绝对是一个错误,而且我绝对不想尝试将其报告给 go 项目。