Wie können Sie UDP-Multicast in Python durchführen?

85

Wie senden und empfangen Sie UDP-Multicast in Python? Gibt es dafür eine Standardbibliothek?

NoName
quelle

Antworten:

96

Das funktioniert bei mir:

Erhalten

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)

Senden

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))

Es basiert auf den Beispielen von http://wiki.python.org/moin/UdpCommunication, die nicht funktioniert haben.

Mein System ist ... Linux 2.6.31-15-generic # 50-Ubuntu SMP Di Nov 10 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4

Gordon Wrigley
quelle
6
Für Mac OS X müssen Sie die Option socket.SO_REUSEPORT als Alternative zu socket.SO_REUSEADDR im obigen Beispiel verwenden, um mehrere Listener für dieselbe Multicast-Port-Adresskombination zuzulassen.
Atikat
Zum Senden brauchte ich auch "sock.bind ((<local ip>, 0))", da mein Multicast-Listener an einen bestimmten Adapter gebunden war.
Mark Foreman
2
Für udp Multicast müssen Sie an eine Multicast-Gruppe / einen Multicast-Port binden, nicht an den lokalen Gruppen-Port. sock.bind((MCAST_GRP, MCAST_PORT))Ihr Code funktioniert möglicherweise nicht und funktioniert möglicherweise nicht. Wenn Sie mehrere Nics haben
stefanB
@atikat: Danke !! Obwohl, warum brauchen wir das auf dem MAC, aber nicht auf Ubuntu?
Kyuubi
2
@ RandallCook: Wenn ich '' durch MCAST_GRP ersetze, erhalte ich socket.error: [Errno 10049] Die angeforderte Adresse ist in ihrem Kontext nicht gültig
stewbasic
17

Multicast-Absender, der an eine Multicast-Gruppe sendet:

#!/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()

Multicast-Empfänger, der aus einer Multicast-Gruppe liest und Hex-Daten an die Konsole druckt:

#!/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()
Niranjan Tulpule
quelle
Ich habe es versucht, es hat nicht funktioniert. In Wireshark kann ich die Übertragung sehen, aber ich sehe keine IGMP-Join-Inhalte und erhalte nichts.
Gordon Wrigley
1
Sie müssen an Multicast-Gruppe / Port nicht lokalen Port auf Multicast-Adresse binden,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB
1
Dieses Beispiel funktioniert aus einem unbekannten Grund nicht für mich. Die Verwendung von socket.gethostbyname (socket.gethostname ()) zur Auswahl der Schnittstelle wählt nicht immer die externe Schnittstelle aus. Auf Debian-Systemen wird in der Tat die Loopback-Adresse ausgewählt. Debian fügt der Host-Tabelle einen Eintrag von 127.0.1.1 für den Hostnamen hinzu. Stattdessen ist es effektiver, socket.INADDR_ANY zu verwenden, das die höherrangige Antwort über die 'pack'-Anweisung verwendet (die korrekter ist als das' + '). Außerdem ist die Verwendung von IP_MULTICAST_IF nicht erforderlich, wie in der höherrangigen Antwort korrekt angegeben.
Brian Bulkowski
1
@BrianBulkowski Es gibt viele Programmierer, die socket.INADDR_ANY verwenden, zum großen Leid und zur Bestürzung derer von uns mit mehreren Schnittstellen, die die Multicast-Daten benötigen, um auf eine bestimmte Schnittstelle zu gelangen. Die Lösung ist nicht socket.INADDR_ANY. Sie müssen die richtige Schnittstelle anhand der IP-Adresse auswählen, wie Sie es für am besten halten (eine Konfigurationsdatei, in der der Endbenutzer gefragt wird, wie auch immer Sie sich für die Anforderungen Ihrer Anwendung entscheiden). socket.INADDR_ANY liefert Ihnen die Multicast-Daten, true, und ist am einfachsten, wenn Sie einen Single-Homed-Host annehmen, aber ich denke, dass dies weniger korrekt ist.
Mike S
@MikeS Obwohl ich Ihnen in einigen Grundsätzen zustimme, ist die Idee, IP-Adressen zur Auswahl von Schnittstellen zu verwenden, furchtbar, furchtbar belastet. Ich kenne das Problem gut, aber in einer dynamischen Welt ist die IP-Adresse nicht die Antwort. Sie müssen also Code schreiben, der alles iteriert und nach dem Schnittstellennamen auswählt, den Schnittstellennamen überprüft, die aktuelle IP-Adresse auswählt und diese verwendet. Hoffentlich hat sich die IP-Adresse in der Zwischenzeit nicht geändert. Ich wünschte, Linux / Unix hätte standardisiert, Schnittstellennamen überall zu verwenden, und die Programmiersprachen hätten dies getan, was eine Konfigurationsdatei sinnvoller machen würde.
Brian Bulkowski
13

Bessere Verwendung:

sock.bind((MCAST_GRP, MCAST_PORT))

anstatt:

sock.bind(('', MCAST_PORT))

Wenn Sie mehrere Multicast-Gruppen am selben Port abhören möchten, erhalten Sie alle Nachrichten von allen Listenern.

st0ne
quelle
6

Um einer Multicast-Gruppe beizutreten, verwendet Python die native OS-Socket-Schnittstelle. Aufgrund der Portabilität und Stabilität der Python-Umgebung werden viele Socket-Optionen direkt an den nativen Socket-Sockoptopt-Aufruf weitergeleitet. Multicast-Betriebsmodi wie das Beitreten und Löschen einer Gruppenmitgliedschaft können setsockoptnur von ausgeführt werden.

Das Basisprogramm zum Empfangen von Multicast-IP-Paketen kann folgendermaßen aussehen:

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)

Erstens wird ein Socket erstellt, gebunden und der Multicast-Gruppenbeitritt durch Ausgabe ausgelöst setsockopt. Am Ende empfängt es Pakete für immer.

Das Senden von Multicast-IP-Frames ist unkompliziert. Wenn Sie eine einzelne Netzwerkkarte in Ihrem System haben, unterscheidet sich das Senden solcher Pakete nicht vom üblichen Senden von UDP-Frames. Sie müssen sich nur um die richtige Ziel-IP-Adresse kümmernsendto() Methode festzulegen.

Mir ist aufgefallen, dass viele Beispiele rund um das Internet tatsächlich zufällig funktionieren. Auch auf offizielle Python-Dokumentation. Das Problem für alle ist, dass struct.pack falsch verwendet wird. Bitte beachten Sie, dass typische Beispiele verwendet werden4sl als Format verwendet wird und nicht mit der tatsächlichen Struktur der OS-Socket-Schnittstelle übereinstimmt.

Ich werde versuchen zu beschreiben, was unter der Haube passiert, wenn der Aufruf von setsockopt für das Python-Socket-Objekt ausgeführt wird.

Python leitet den Methodenaufruf setsockopt an die native C-Socket-Schnittstelle weiter. In der Linux-Socket-Dokumentation (siehe man 7 ip) werden zwei ip_mreqnStrukturformen für die Option IP_ADD_MEMBERSHIP eingeführt. Die kürzeste Form ist 8 Byte lang und die längere 12 Byte. Das obige Beispiel generiert einen 8-Byte- setsockoptAufruf, bei dem die ersten vier Bytes multicast_groupund die zweiten vier Bytes definiert werden interface_ip.

Leszek Wojcik
quelle
2

Schauen Sie sich py-multicast an . Das Netzwerkmodul kann prüfen, ob eine Schnittstelle Multicast unterstützt (zumindest unter Linux).

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

Vielleicht wurden Probleme mit dem Nicht-Sehen von IGMP durch eine Schnittstelle verursacht, die Multicast nicht unterstützt?

wroniasty
quelle
2

Nur eine weitere Antwort, um einige subtile Punkte im Code der anderen Antworten zu erklären:

  • socket.INADDR_ANY - (Bearbeitet) Im Kontext von IP_ADD_MEMBERSHIP bindet dies den Socket nicht wirklich an alle Schnittstellen, sondern wählt einfach die Standardschnittstelle, an der Multicast aktiv ist (gemäß Routing-Tabelle).
  • Das Beitreten zu einer Multicast-Gruppe ist nicht dasselbe wie das Binden eines Sockets an eine lokale Schnittstellenadresse

Siehe Was bedeutet es, einen Multicast-Socket (UDP) zu binden?Weitere Informationen zur Funktionsweise von Multicast

Multicast-Empfänger:

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)

Beispielnutzung: (Führen Sie die folgenden Schritte in zwei Konsolen aus und wählen Sie Ihr eigenes --iface aus (muss mit der Schnittstelle identisch sein, die die Multicast-Daten empfängt))

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'

Multicast-Absender:

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)

Beispielverwendung: # Angenommen, der Empfänger bindet an die unten angegebene Multicast-Gruppenadresse und einige Programmanfragen, um dieser Gruppe beizutreten. Um den Fall zu vereinfachen, nehmen Sie an, dass sich Empfänger und Absender im selben Subnetz befinden

python3 multicast_send.py --mcast-group '224.1.1.2'

python3 multicast_send.py --mcast-group '224.1.1.4'

Pterodragon
quelle
INADDR_ANY ist nicht ‚wählen Sie eine der lokalen Schnittstellen]‘.
Marquis von Lorne
0

Damit der Clientcode (von tolomea) unter Solaris funktioniert, müssen Sie den ttl-Wert für die IP_MULTICAST_TTLSocket-Option als vorzeichenloses Zeichen übergeben. Andernfalls erhalten Sie eine Fehlermeldung. Dies funktionierte für mich unter Solaris 10 und 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))
Häschen
quelle
-1

Tolomeas Antwort hat bei mir funktioniert. Ich habe es auch in socketserver.UDPServer gehackt :

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)
Tompreston
quelle