Итак, мне наконец удалось решить мою проблему.
Ответ Гамильтона был очень уместным и приблизил решение, но, как он упомянул, оно может не работать (и не будет) в Windows.
Решение Linux почти работает на обоих, но есть несколько очень раздражающих различий с сокетами Wondows:
- Вы не можете привязать сокет к широковещательному адресу. (ни один из
n.n.n.255
или 255.255.255.255
не работает)
- Вы можете привязать сокет к anycast (
0.0.0.0
) или любому действительному локальному адресу, и он будет получать только одноадресные сообщения.
- Если вы установите socketopt
SO_BROADCAST
перед привязкой, он также будет принимать одноадресные и широковещательные сообщения (и вы не заметите разницу).
- Если вы хотите привязать несколько сокетов к одному и тому же порту, вы должны установить socketopt
SO_REUSEADDR
для всех сокетов.
- Если вы создаете одноадресную рассылку и сокет «Bothcast» и привязываете к одному и тому же порту, вы в конечном итоге получаете оба сообщения одноадресной рассылки.
- Если вы используете
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)