IP地址0.0.0.0在Windows与Linux下的不同

IP地址0.0.0.0在Windows与Linux下的不同

0.0.0.0这个地址

0.0.0.0是一个很好玩的IP地址。Wikipedia从Official meaning and use、Operating system specific uses、Other non-standard uses、Routing几个不同的角度对它进行了说明。它的用途远比作为参数传给bind多。

几条说明中,最引人注目的还是Operating system specific usesOther non-standard uses。这两个条目说明,关于0.0.0.0的含义和用途甚至没有一个统一的看法。它在不同操作系统下的表现也(可能)不同。

我们知道,bind0.0.0.0相当于bind到了所有的接口上。那么,在这些接口中,如果有的被占用,有的处于空闲状态,结果会如何呢?换句话说,这个bind可以绑定端口,但不能绑定所有的端口。

用下面的python代码在Linux和Windows上分别进行测试。这段代码尝试同时绑定127.0.0.10.0.0.0的TCP 33333端口,并打印进来的连接:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import selectors
import socket

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print(str(sock) + 'accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

def build_socket(ip, port):
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(100)
    sock.setblocking(False)
    return sock

if __name__ =='__main__':

    sel = selectors.DefaultSelector()

    sock = build_socket('127.0.0.1', 33333)
    sel.register(sock, selectors.EVENT_READ, accept)
    sock = build_socket('0.0.0.0', 33333)
    sel.register(sock, selectors.EVENT_READ, accept)
    
    while True:
        events = sel.select()
        for key, mask in events:
            callback = key.data
            callback(key.fileobj, mask)

Linux下的结果

Linux直接甩了一个错误过来:

Linux同时绑定0.0.0.0和127.0.0.1

说明Linux不支持所谓partial bind。先bind 0.0.0.0bind 127.0.0.1会导致第二个bind失败。老实说,这与我的预期也是一致的。一个操作只能完成一半,显然不能认为是成功的。

Windows下的结果

Windows下的结果就比较出乎意料了。这两个bind都能成功,而且127.0.0.1bind的优先级较0.0.0.0高。用telnet测试连接,发现不管是先绑定127.0.0.1还是先绑定0.0.0.0telnet 127.0.0.1 33333的连接总是被127.0.0.1截获:

Windows同时绑定0.0.0.0和127.0.0.1

可能是127.0.0.1的指称比0.0.0.0更具体,就像路由表的匹配一样,Windows把两个方向都可以的流量分给了127.0.0.1。但这种静默操作会导致很大的问题。如果你的程序监听了0.0.0.0,而某个不知名的程序监听了127.0.0.1,那你的程序就收不到来自127.0.0.1的消息,而整个过程都不会有任何错误提示。

总结

总之,Linux和Windows对0.0.0.0的处理确实是不太一样的。与Linux不同,Windows把路由优先级那一套搬到了bind上面。如果bind 0.0.0.0收不到localhost的数据,不妨用resmon看一下是不是有哪个进程bindlocalhost,这种情况下Windows不会报错,只会静默地把你期望的包丢给另一个进程。