端口8888已通过在docker容器中运行的进程绑定到我的(OS X 10.13.5)系统上:
$ netstat -an | grep 8888
tcp6 0 0 ::1.8888 *.* LISTEN
tcp4 0 0 *.8888 *.* LISTEN
试图绑定到该端口的python程序(使用我可以管理的与golang尽可能接近的socket选项),以我期望的方式失败:
import socket
import fcntl
import os
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
flag = fcntl.fcntl(sock.fileno(), fcntl.F_GETFL)
fcntl.fcntl(sock.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(("0.0.0.0", 8888))
sock.listen(5)
main()
失败:
$ 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
但是,通过net.Listen
创建连接的go程序不会失败,正如我期望的那样:
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")
}
}
成功:
$ go run test.go
Listening
一位同事报告说,使用相同的设置,他的Ubuntu系统正确地使go程序失败。
为什么在Mac上这会成功,如何获取网络。听一听显示绑定到端口8888时出现错误?
编辑:如果我使用简单的go程序占用端口8888:
package main
import (
"log"
"net/http"
)
func main() {
log.Fatal(http.ListenAndServe("0.0.0.0:8888", nil))
}
然后test.go
无法正确绑定到端口。但是docker进程(基本上在运行^^^)不会导致它失败。
edit 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
因此,指定“ tcp4”将解决我的实际问题,但是我真的很想了解“ tcp46”连接类型到底是怎么回事,而我找不到任何文档。救命!
答案 0 :(得分:1)
好的,我想我有一个故事来讲述为什么发生这种情况:
0.0.0.0:<port>
和IPv6 [::1]:<port>
。请注意,在IPv6上,它映射到localhost
的等效项,而不是0.0.0.0
的{{1}}![::]
类型,因此,如果您有很好的文档,请请指向我!)。tcp46
上打开一个IPv6套接字,相当于IPv6中的[::]:8888
。之所以成功,是因为docker正在监听0.0.0.0:8888
(相当于127.0.0.1),不是 [::1]
我的同事报告说,在Ubuntu上,docker监听[::]
,这就是为什么他无法重现我看到的问题的原因。这似乎是明智的行为!而且我不知道为什么在Mac上不这样做。
我还认为,即使Go创建的套接字实际上很难访问,它在这种情况下成功地获得成功还是令人惊讶的,甚至可能有点错误。但是我不能说这绝对是一个错误,而且我绝对不想尝试将其报告给go项目。
答案 1 :(得分:1)
关于tcp46
的{{1}}输出,我找不到任何文档,但确实找到了相关来源。
来自network_cmds-543/netstat.proj/inet.c:
netstat
void
protopr(uint32_t proto, /* for sysctl version we pass proto # */
char *name, int af)
{
...
struct xinpcb_n *inp = NULL;
...
const char *vchar;
#ifdef INET6
if ((inp->inp_vflag & INP_IPV6) != 0)
vchar = ((inp->inp_vflag & INP_IPV4) != 0)
? "46" : "6 ";
else
#endif
vchar = ((inp->inp_vflag & INP_IPV4) != 0)
? "4 " : " ";
在bsd/netinet/in_pcb.h中定义。
xinpcb_n
在该文件的其他位置, * struct inpcb captures the network layer state for TCP, UDP and raw IPv6
* and IPv6 sockets.
被记录为inp_vflag
。依次将它们定义为:
/* INP_IPV4 or INP_IPV6 */
因此,基本上,当套接字同时设置了v4和v6位时,它将在协议列中显示#define INP_IPV4 0x1
#define INP_IPV6 0x2
。
关于Go,有this socket()
function in the net package:
46
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
...
if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
具有每个平台的定义,以下是the BSD variant的摘录:
setDefaultSockopts()
因此,套接字上允许同时使用两个IP版本都是由func setDefaultSockopts(s, family, sotype int, ipv6only bool) error {
...
if supportsIPv4map() && family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only))
布尔值驱动的。经过进一步的挖掘,我发现了where this is decided并提供了有关逻辑的详细说明:
ipv6Only