Come si esegue il multicast UDP in Python?


88

Come invii e ricevi il multicast UDP in Python? Esiste una libreria standard per farlo?

Risposte:


101

Questo funziona per me:

Ricevere

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)

Spedire

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

Si basa sugli esempi di http://wiki.python.org/moin/UdpCommunication che non hanno funzionato.

Il mio sistema è ... Linux 2.6.31-15-generic # 50-Ubuntu SMP Mar 10 nov 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
Per mac os x è necessario utilizzare l'opzione socket.SO_REUSEPORT come alternativa a socket.SO_REUSEADDR nell'esempio precedente, per consentire più listener sulla stessa combinazione di indirizzi di porta multicast.
atikat

Per l'invio, avevo anche bisogno di "sock.bind ((<local ip>, 0))" perché il mio ascoltatore multicast era associato a un adattatore specifico.
Mark Foreman

2
per udp multicast è necessario collegarsi al gruppo / porta multicast non alla porta del gruppo locale sock.bind((MCAST_GRP, MCAST_PORT)), il codice potrebbe e potrebbe non funzionare, potrebbe non funzionare quando si hanno più
schede di rete

@atikat: grazie !! Anche se perché ne abbiamo bisogno sul MAC ma non su Ubuntu?
Kyuubi

2
@RandallCook: Quando sostituisco "" con MCAST_GRP ottengo socket.error: [Errno 10049] L'indirizzo richiesto non è valido nel suo contesto
stewbasic

17

Mittente multicast che trasmette a un gruppo multicast:

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

Ricevitore multicast che legge da un gruppo multicast e stampa dati esadecimali sulla console:

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

Ho provato questo, non ha funzionato. In Wireshark posso vedere la trasmissione, ma non vedo alcun elemento di join IGMP e non ricevo nulla.
Gordon Wrigley,

1
è necessario collegarsi al gruppo / porta multicast non alla porta locale sull'indirizzo multicast,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
Questo esempio non funziona per me, per un motivo oscuro. Usare socket.gethostbyname (socket.gethostname ()) per selezionare l'interfaccia non sempre seleziona l'interfaccia esterna - infatti, sui sistemi Debian, tende a selezionare l'indirizzo di loopback. Debian aggiunge una voce di 127.0.1.1 nella tabella host per il nome host. Invece, è più efficace usare socket.INADDR_ANY, che la risposta di rango più alto utilizza attraverso l'istruzione 'pack' (che è più corretta del '+'). Inoltre, l'uso di IP_MULTICAST_IF non è richiesto, poiché la risposta con ranking più alto afferma correttamente.
Brian Bulkowski

1
@BrianBulkowski ci sono molti programmatori che usano socket.INADDR_ANY, con grande dolore e costernazione di quelli di noi con più interfacce, che hanno bisogno dei dati multicast per venire su una particolare interfaccia. La soluzione non è socket.INADDR_ANY. Serve a selezionare l'interfaccia corretta in base all'indirizzo IP, comunque ritieni sia la cosa migliore (un file di configurazione, che chiede all'utente finale, comunque tu scelga per le esigenze della tua applicazione). socket.INADDR_ANY ti fornirà i dati multicast, true, ed è più semplice se presumi un host single-homed, ma penso che sia meno corretto.
Mike S

@ MikeS mentre sono d'accordo con te su alcuni principi, l'idea di utilizzare indirizzi IP per selezionare le interfacce è terribilmente, terribilmente complicata. Conosco bene il problema, ma in un mondo dinamico e l'indirizzo IP non è la risposta. Quindi è necessario scrivere codice che itera tutto e sceglie in base al nome dell'interfaccia, guarda il nome dell'interfaccia, sceglie l'indirizzo IP corrente e lo usa. Si spera che nel frattempo l'indirizzo IP non sia cambiato. Vorrei che Linux / Unix si fosse standardizzato sull'uso di nomi di interfaccia ovunque, e i linguaggi di programmazione lo avessero fatto, ciò avrebbe reso un file di configurazione più sensato.
Brian Bulkowski

13

Migliore utilizzo:

sock.bind((MCAST_GRP, MCAST_PORT))

invece di:

sock.bind(('', MCAST_PORT))

perché, se desideri ascoltare più gruppi multicast sulla stessa porta, riceverai tutti i messaggi su tutti i listener.


6

Per entrare a far parte del gruppo multicast, Python utilizza l'interfaccia socket del sistema operativo nativo. A causa della portabilità e stabilità dell'ambiente Python, molte delle opzioni del socket vengono inoltrate direttamente alla chiamata del socket nativo setsockopt. Modalità multicast di funzionamento come l'adesione e rilasciando l'appartenenza al gruppo può essere realizzato setsockoptsoltanto.

Il programma di base per la ricezione di pacchetti IP multicast può essere simile a:

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)

In primo luogo crea il socket, lo associa e innesca l'adesione al gruppo multicast mediante emissione setsockopt. Alla fine riceve pacchetti per sempre.

L'invio di frame IP multicast è semplice. Se si dispone di una singola NIC nel sistema, l'invio di tali pacchetti non differisce dall'invio di frame UDP normali. Tutto ciò di cui devi occuparti è impostare l'indirizzo IP di destinazione corretto nel sendto()metodo.

Ho notato che molti esempi su Internet funzionano per caso. Anche sulla documentazione ufficiale di Python. Il problema per tutti loro sta usando struct.pack in modo errato. Si noti che l'esempio tipico utilizza 4slcome formato e non è allineato con la struttura dell'interfaccia socket del sistema operativo effettiva.

Cercherò di descrivere cosa succede sotto il cofano quando si esercita la chiamata setsockopt per l'oggetto socket python.

Python inoltra la chiamata al metodo setsockopt all'interfaccia socket C nativa. La documentazione del socket Linux (vedere man 7 ip) introduce due forme di ip_mreqnstruttura per l'opzione IP_ADD_MEMBERSHIP. La forma più breve è lunga 8 byte e quella più lunga è lunga 12 byte. L'esempio sopra genera una setsockoptchiamata a 8 byte in cui i primi quattro byte definiscono multicast_groupe i secondi quattro byte definiscono interface_ip.


2

Dai un'occhiata a py-multicast . Il modulo di rete può verificare se un'interfaccia supporta il multicast (almeno su 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

Forse i problemi con la mancata visualizzazione di IGMP sono stati causati da un'interfaccia che non supportava il multicast?


2

Solo un'altra risposta per spiegare alcuni punti sottili nel codice delle altre risposte:

  • socket.INADDR_ANY- (Modificato) Nel contesto di IP_ADD_MEMBERSHIP, questo non lega realmente il socket a tutte le interfacce, ma sceglie semplicemente l'interfaccia predefinita in cui è attivo il multicast (secondo la tabella di instradamento)
  • Partecipare a un gruppo multicast non è la stessa cosa che associare un socket a un indirizzo di interfaccia locale

vedere Cosa significa associare un socket multicast (UDP)? per ulteriori informazioni su come funziona il multicast

Ricevitore multicast:

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)

utilizzo di esempio: (eseguire quanto segue in due console e scegliere il proprio --iface (deve essere uguale all'interfaccia che riceve i dati multicast))

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'

Mittente multicast:

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)

esempio di utilizzo: # si presume che il ricevitore si colleghi al seguente indirizzo di gruppo multicast e che alcuni programmi richiedano di unirsi a quel gruppo. E per semplificare il caso, supponiamo che il destinatario e il mittente si trovino nella stessa sottorete

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

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


INADDR_ANY non "sceglie una delle interfacce locali]".
user207421

0

Per far funzionare il codice client (da tolomea) su Solaris, è necessario passare il valore ttl per l' IP_MULTICAST_TTLopzione socket come carattere non firmato. Altrimenti riceverai un errore. Questo ha funzionato per me su Solaris 10 e 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))

0

Questo esempio non funziona per me, per un motivo oscuro.

Non oscuro, è semplice instradamento.

Su OpenBSD

route add -inet 224.0.0.0/4 224.0.0.1

Puoi impostare il percorso su uno sviluppatore su Linux

route add -net 224.0.0.0 netmask 240.0.0.0 dev wlp2s0

forza tutto il traffico multicast a un'interfaccia su Linux

   ifconfig wlp2s0 allmulti

tcpdump è semplicissimo

tcpdump -n multicast

Nel tuo codice hai:

while True:
  # For Python 3, change next line to "print(sock.recv(10240))"

Perché 10240 ?

la dimensione del pacchetto multicast dovrebbe essere 1316 byte


-1

La risposta di Tolomea ha funzionato per me. L'ho hackerato anche in socketserver.UDPServer :

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.