为什么不能同时绑定到0.0.0.0:80和192.168.1.1:80?

8
我的Python测试代码:
import socket

s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s1.bind(('192.168.1.1', 80)) 
s1.listen(5)

s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2.bind(('0.0.0.0', 80)) 
s2.listen(5)

我遇到了这个错误:

fpemud-workstation test # ./test.py
Traceback (most recent call last):
  File "./test.py", line 11, in <module>
    s2.bind(('0.0.0.0', 80)) 
  File "/usr/lib64/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 98] Address already in use

192.168.1.1是我的eth0接口的IP地址。
我认为0.0.0.0:80和192.168.1.1:80应该能够共存。
目标地址为192.168.1.1的数据包发送到套接字s1,其他目标地址的数据包发送到套接字s2。


因为在你运行这个网络的时候,这台电脑的IP是192.168.1.1,它与0.0.0.0和127.0.0.1相同,实际上,IP地址并不重要,端口号才是问题。 - user2511414
1
尽管已经发布了答案,但请先绑定通配符套接字*,然后再绑定另一个。 - Celada
3个回答

15

您不能同时将0.0.0.0:80和其他任何IP绑定到端口80上,因为0.0.0.0覆盖了机器上存在的每个IP,包括您的192.168.1.1地址。它并不意味着“任何其他目标地址”,而是意味着“此框中的所有接口”。


你的意思是这是一个规范。为什么操作系统(Linux)使用这个规范?服务器套接字0.0.0.0:80和192.168.0.1:80的共存是否会引起任何问题? - fpemud
我想运行2个Samba服务器,服务器A用于接口br0,服务器B用于所有其他接口(包括动态创建的接口)。现在由于规格的限制,我无法这样做。 - fpemud
2
如果0.0.0.0表示“在此端口上未绑定的所有地址”,那将是不确定的。如果我说0.0.0.0,我希望这台计算机响应的每个地址都能到达该端口。如果您的示例中的192.168.0.1进程关闭会发生什么?0.0.0.0进程是否接管该绑定?我不希望代码做出这样的反应,C代码也不应该自作主张。但是,如果没有这样做,则对0.0.0.0的绑定是无用的,因为它实际上并不是每个端口或每个可用端口,而是在系统调用时打开的每个端口。 - Kevin
1
@fpemud 很抱歉,这是网络工作的基本原理,没有绕过它的方法。任何行为不同的TCP堆栈都是基本有问题的,所以你不会找到任何能够满足你需求的实现。抱歉! - Ben
@JamesHolderness 我改口了!这个链接非常有趣:https://lwn.net/Articles/542629/ - Ben
显示剩余2条评论

1
因为这是一个自相矛盾的说法。0.0.0.0表示“接受来自任何本地IP地址的连接”。192.168.1.1表示“只接受寻址到192.168.1.1的连接”。如果有人连接到192.168.1.1,您到底希望发生什么?

1
尽管其他答案说不行,但这是可能的——只是bind的工作方式取决于具体实现。例如,在Windows上,您的代码可能可以正常工作。在某些*nix操作系统上,我认为您可以通过设置SO_REUSEADDR套接字选项使其正常工作。在Linux上,我已经能够使用SO_REUSEPORT套接字选项使其正常工作,但仅适用于3.9或更高版本的内核。
不幸的是,当前版本的Python不直接支持SO_REUSEPORT属性,因此我们必须手动定义它。
基本上,您的代码应该像这样:
# This adds support for the SO_REUSEPORT constant if not already defined.
if not hasattr(socket, 'SO_REUSEPORT'):
  socket.SO_REUSEPORT = 15

s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s1.bind(('192.168.1.1', 80)) 
s1.listen(5)

s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s2.bind(('0.0.0.0', 80)) 
s2.listen(5)

SO_REUSEADDR只适用于TIME_WAIT状态。序列开关没有影响。我也不认为Windows有不同的行为。 - fpemud
请查看这个StackOverflow答案,以获取有关SO_REUSEADDR不同实现的更多信息。我在Windows(至少是Windows Vista)上测试了您的原始代码,所以我知道那可以工作。我刚刚有机会在Linux上进行测试,但我无法使其工作,但这可能与版本有关。我现在没有时间进一步研究,但如果我发现其他任何信息,我将稍后更新。 - James Holderness
@fpemud 我有机会进行了更多的研究,并且已经能够使用SO_REUSEPORT使其工作,但仅适用于3.9或更高版本的内核。我的答案中更新了代码。 - James Holderness

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接