Jak wysyłać i odbierać multiemisję UDP w Pythonie? Czy jest do tego standardowa biblioteka?
Odpowiedzi:
To działa dla mnie:
Otrzymać
import socket
import struct
MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
IS_ALL_GROUPS = True
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if IS_ALL_GROUPS:
# on this port, receives ALL multicast groups
sock.bind(('', MCAST_PORT))
else:
# on this port, listen ONLY to MCAST_GRP
sock.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
# For Python 3, change next line to "print(sock.recv(10240))"
print sock.recv(10240)
Wysłać
import socket
MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
# regarding socket.IP_MULTICAST_TTL
# ---------------------------------
# for all packets sent, after two hops on the network the packet will not
# be re-sent/broadcast (see https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html)
MULTICAST_TTL = 2
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL)
# For Python 3, change next line to 'sock.sendto(b"robot", ...' to avoid the
# "bytes-like object is required" msg (https://stackoverflow.com/a/42612820)
sock.sendto("robot", (MCAST_GRP, MCAST_PORT))
Opiera się na przykładach z http://wiki.python.org/moin/UdpCommunication, które nie działały.
Mój system to ... Linux 2.6.31-15-generic # 50-Ubuntu SMP Wt 10 Lis 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4
sock.bind((MCAST_GRP, MCAST_PORT))
, Twój kod może i może nie działać, może nie działać, gdy masz wiele
Nadawca multiemisji, który rozgłasza do grupy multiemisji:
#!/usr/bin/env python
import socket
import struct
def main():
MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
sock.sendto('Hello World!', (MCAST_GRP, MCAST_PORT))
if __name__ == '__main__':
main()
Odbiornik multiemisji, który czyta z grupy multiemisji i drukuje dane szesnastkowe na konsoli:
#!/usr/bin/env python
import socket
import binascii
def main():
MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except AttributeError:
pass
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
sock.bind((MCAST_GRP, MCAST_PORT))
host = socket.gethostbyname(socket.gethostname())
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(host))
sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP,
socket.inet_aton(MCAST_GRP) + socket.inet_aton(host))
while 1:
try:
data, addr = sock.recvfrom(1024)
except socket.error, e:
print 'Expection'
hexdata = binascii.hexlify(data)
print 'Data = %s' % hexdata
if __name__ == '__main__':
main()
sock.bind((MCAST_GRP, MCAST_PORT))
Aby dołączyć do grupy multiemisji, Python używa natywnego interfejsu gniazda systemu operacyjnego. Ze względu na przenośność i stabilność środowiska Pythona, wiele opcji gniazd jest bezpośrednio przekazywanych do natywnego wywołania setockopt gniazda. Tryb pracy w trybie multiemisji, taki jak dołączanie i usuwanie członkostwa w grupie, może być realizowany setsockopt
tylko przez .
Podstawowy program do odbierania pakietów multicast IP może wyglądać następująco:
from socket import *
multicast_port = 55555
multicast_group = "224.1.1.1"
interface_ip = "10.11.1.43"
s = socket(AF_INET, SOCK_DGRAM )
s.bind(("", multicast_port ))
mreq = inet_aton(multicast_group) + inet_aton(interface_ip)
s.setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, str(mreq))
while 1:
print s.recv(1500)
Najpierw tworzy gniazdo, wiąże je i wyzwala dołączanie do grup multiemisji przez wydanie setsockopt
. Na samym końcu otrzymuje pakiety na zawsze.
Wysyłanie ramek IP w trybie multiemisji jest proste. Jeśli masz jedną kartę sieciową w swoim systemie, wysyłanie takich pakietów nie różni się od zwykłego wysyłania ramek UDP. Wystarczy, że w sendto()
metodzie ustawisz poprawny docelowy adres IP .
Zauważyłem, że wiele przykładów z Internetu działa przypadkowo. Nawet w oficjalnej dokumentacji Pythona. Problem dla wszystkich z nich używa niepoprawnie struct.pack. Należy pamiętać, że typowy przykład używa 4sl
jako formatu i nie jest zgodny z rzeczywistą strukturą interfejsu gniazda systemu operacyjnego.
Postaram się opisać, co dzieje się pod maską podczas ćwiczenia wywołania setsockopt dla obiektu gniazda Pythona.
Python przekazuje wywołanie metody setsockopt do natywnego interfejsu gniazda C. Dokumentacja gniazd Linuksa (zobacz man 7 ip
) wprowadza dwie formy ip_mreqn
struktury dla opcji IP_ADD_MEMBERSHIP. Najkrótszy formularz ma długość 8 bajtów, a dłuższy 12 bajtów. Powyższy przykład generuje setsockopt
wywołanie 8-bajtowe, w którym zdefiniowane są pierwsze cztery bajty, multicast_group
a drugie cztery bajty interface_ip
.
Spójrz na py-multicast . Moduł sieciowy może sprawdzić, czy interfejs obsługuje multiemisję (przynajmniej na Linuksie).
import multicast
from multicast import network
receiver = multicast.MulticastUDPReceiver ("eth0", "238.0.0.1", 1234 )
data = receiver.read()
receiver.close()
config = network.ifconfig()
print config['eth0'].addresses
# ['10.0.0.1']
print config['eth0'].multicast
#True - eth0 supports multicast
print config['eth0'].up
#True - eth0 is up
Być może problemy z brakiem widoczności IGMP były spowodowane przez interfejs nie obsługujący multiemisji?
Jeszcze jedna odpowiedź, aby wyjaśnić niektóre subtelne punkty w kodzie innych odpowiedzi:
socket.INADDR_ANY
- (Edytowano) W kontekście IP_ADD_MEMBERSHIP
, to tak naprawdę nie wiąże gniazda ze wszystkimi interfejsami, ale po prostu wybiera domyślny interfejs, w którym działa multiemisja (zgodnie z tabelą routingu)zobacz Co to znaczy wiązać gniazdo multiemisji (UDP)? aby uzyskać więcej informacji na temat działania multiemisji
Odbiornik multiemisji:
import socket
import struct
import argparse
def run(groups, port, iface=None, bind_group=None):
# generally speaking you want to bind to one of the groups you joined in
# this script,
# but it is also possible to bind to group which is added by some other
# programs (like another python program instance of this)
# assert bind_group in groups + [None], \
# 'bind group not in groups to join'
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# allow reuse of socket (to allow another instance of python running this
# script binding to the same ip/port)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('' if bind_group is None else bind_group, port))
for group in groups:
mreq = struct.pack(
'4sl' if iface is None else '4s4s',
socket.inet_aton(group),
socket.INADDR_ANY if iface is None else socket.inet_aton(iface))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
print(sock.recv(10240))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--port', type=int, default=19900)
parser.add_argument('--join-mcast-groups', default=[], nargs='*',
help='multicast groups (ip addrs) to listen to join')
parser.add_argument(
'--iface', default=None,
help='local interface to use for listening to multicast data; '
'if unspecified, any interface would be chosen')
parser.add_argument(
'--bind-group', default=None,
help='multicast groups (ip addrs) to bind to for the udp socket; '
'should be one of the multicast groups joined globally '
'(not necessarily joined in this python program) '
'in the interface specified by --iface. '
'If unspecified, bind to 0.0.0.0 '
'(all addresses (all multicast addresses) of that interface)')
args = parser.parse_args()
run(args.join_mcast_groups, args.port, args.iface, args.bind_group)
przykładowe użycie: (uruchom poniższe na dwóch konsolach i wybierz własny --iface (musi być taki sam jak interfejs, który odbiera dane multiemisji))
python3 multicast_recv.py --iface='192.168.56.102' --join-mcast-groups '224.1.1.1' '224.1.1.2' '224.1.1.3' --bind-group '224.1.1.2'
python3 multicast_recv.py --iface='192.168.56.102' --join-mcast-groups '224.1.1.4'
Nadawca multiemisji:
import socket
import argparse
def run(group, port):
MULTICAST_TTL = 20
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, MULTICAST_TTL)
sock.sendto(b'from multicast_send.py: ' +
f'group: {group}, port: {port}'.encode(), (group, port))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--mcast-group', default='224.1.1.1')
parser.add_argument('--port', default=19900)
args = parser.parse_args()
run(args.mcast_group, args.port)
przykładowe użycie: # załóżmy, że odbiorca łączy się z poniższym adresem grupowym multiemisji i że jakiś program żąda przyłączenia się do tej grupy. Aby uprościć sprawę, załóżmy, że odbiorca i nadawca znajdują się w tej samej podsieci
python3 multicast_send.py --mcast-group '224.1.1.2'
python3 multicast_send.py --mcast-group '224.1.1.4'
Aby kod klienta (z tolomea) działał w systemie Solaris, musisz przekazać wartość ttl dla IP_MULTICAST_TTL
opcji gniazda jako znak bez znaku. W przeciwnym razie pojawi się błąd. To zadziałało dla mnie na Solarisie 10 i 11:
import socket
import struct
MCAST_GRP = '224.1.1.1'
MCAST_PORT = 5007
ttl = struct.pack('B', 2)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
sock.sendto("robot", (MCAST_GRP, MCAST_PORT))
odpowiedź tolomei zadziałała dla mnie. Włamałem go do socketserver.UDPServer też:
class ThreadedMulticastServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
def __init__(self, *args):
super().__init__(*args)
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((MCAST_GRP, MCAST_PORT))
mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)