所以我终于设法解决了我的问题。
汉密尔顿的回答非常合适,并使解决方案更接近,但正如他所提到的,它可能在 Windows 上不起作用(并且不会)。
Linux 解决方案几乎都适用于这两者,但与 Wondows 套接字有一些非常烦人的区别:
- 您不能将套接字绑定到广播地址。 (
n.n.n.255 或 255.255.255.255 都不起作用)
- 您可以将套接字绑定到任播 (
0.0.0.0) 或任何有效的本地地址,它将只接收单播消息。
- 如果您在绑定之前设置了 socketopt
SO_BROADCAST,它也会接收单播和广播消息(而且您不会区分)。
- 如果要将多个套接字绑定到同一个端口,则必须在所有套接字上设置 socketopt
SO_REUSEADDR。
- 如果您构造一个单播和一个“双播”套接字并绑定到同一个端口,那么您最终都会收到单播消息。
- 如果您使用
select(在Linux 示例中由这两个套接字提供)并且它们接收到相同的单播消息,select 只在第一轮返回一个。 (这是 Windows 10 的个人实验)
我最终得到了这个解决方法:
import platform
from socket import gethostbyname, gethostname, socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR
from select import select
PORT = 9999
myPublicIP = gethostbyname(gethostname())
platform = platform.system()
bsock = socket(AF_INET, SOCK_DGRAM)
usock = socket(AF_INET, SOCK_DGRAM)
if platform == 'Windows':
usock.setsockopt(SOL_SOCKET, SO_BROADCAST, 0) # This isn't necessary
usock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
bsock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
bsock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
usock.bind(('', PORT))
bsock.bind(('', PORT))
# empty string means anycast
elif platform == 'Linux':
# on Linux SO_BROADCAST only affects the sending
usock.bind((myPublicIP, PORT))
bsock.bind(('255.255.255.255', PORT))
while True:
# This is the tricky part
select([bsock, usock], [], [])
# if a bcast msg received the first select gives back only the usock instead of both
ready = select([bsock, usock], [], [])[0]
# the second select will give back both at the same time
if bsock in ready and usock in ready:
bmsg = bsock.recvfrom(1024)
umsg = usock.recvfrom(1024)
in_msg, in_addr = bmsg
print("Got broadcast msg", in_msg, "from", in_addr)
if bmsg != umsg:
in_msg, in_addr = umsg
print("Got unicast msg", in_msg, "from", in_addr)
else:
if bsock in ready:
in_msg, in_addr = bsock.recvfrom(1024)
print("Got broadcast msg", in_msg, "from", in_addr)
if usock in ready:
in_msg, in_addr = usock.recvfrom(1024)
print("Got unicast msg", in_msg, "from", in_addr)