Comment faites-vous la multidiffusion UDP en Python?


86

Comment envoyer et recevoir la multidiffusion UDP en Python? Existe-t-il une bibliothèque standard pour le faire?

Réponses:


98

Cela fonctionne pour moi:

Recevoir

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)

Envoyer

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

Il est basé sur les exemples de http://wiki.python.org/moin/UdpCommunication qui n'ont pas fonctionné.

Mon système est ... Linux 2.6.31-15-generic # 50-Ubuntu SMP mar 10 novembre 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
Pour mac os x, vous devez utiliser l'option socket.SO_REUSEPORT comme alternative à socket.SO_REUSEADDR dans l'exemple ci-dessus, pour autoriser plusieurs écouteurs sur la même combinaison d'adresses de port de multidiffusion.
atikat

Pour l'envoi, j'avais également besoin de "sock.bind ((<local ip>, 0))" car mon auditeur multicast était lié à un adaptateur spécifique.
Mark Foreman

2
pour la multidiffusion udp, vous devez vous lier au groupe / port de multidiffusion et non au port du groupe local sock.bind((MCAST_GRP, MCAST_PORT)), votre code peut et peut ne pas fonctionner, il peut ne pas fonctionner lorsque vous avez plusieurs
nics

@atikat: Merci !! Bien que pourquoi en avons-nous besoin sur le MAC mais pas sur Ubuntu?
Kyuubi

2
@RandallCook: Quand je remplace '' par MCAST_GRP j'obtiens socket.error: [Errno 10049] L'adresse demandée n'est pas valide dans son contexte
stewbasic

17

Expéditeur de multidiffusion qui diffuse vers un groupe de multidiffusion:

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

Récepteur de multidiffusion qui lit à partir d'un groupe de multidiffusion et imprime des données hexadécimales sur la 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()

J'ai essayé ça, ça n'a pas marché. Dans Wireshark, je peux voir la transmission, mais je ne vois aucun élément de jointure IGMP et je ne reçois rien.
Gordon Wrigley

1
vous devez vous lier au groupe de multidiffusion / port et non au port local sur l'adresse de multidiffusion,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
Cet exemple ne fonctionne pas pour moi, pour une raison obscure. Utiliser socket.gethostbyname (socket.gethostname ()) pour sélectionner l'interface n'élit pas toujours l'interface externe - en fait, sur les systèmes Debian, il a tendance à sélectionner l'adresse de bouclage. Debian ajoute une entrée de 127.0.1.1 dans la table d'hôtes pour le nom d'hôte. Au lieu de cela, il est plus efficace d'utiliser socket.INADDR_ANY, que la réponse de rang supérieur utilise via l'instruction 'pack' (qui est plus correcte que le '+'). De plus, l'utilisation de IP_MULTICAST_IF n'est pas requise, car la réponse de rang supérieur l'indique correctement.
Brian Bulkowski

1
@BrianBulkowski il y a beaucoup de programmeurs qui utilisent socket.INADDR_ANY, au grand malheur et à la consternation de ceux d'entre nous qui ont plusieurs interfaces, qui ont besoin que les données multicast arrivent sur une interface particulière. La solution n'est pas socket.INADDR_ANY. Il s'agit de sélectionner la bonne interface par adresse IP, comme vous le jugez le mieux (un fichier de configuration, demandant à l'utilisateur final, quelle que soit la manière dont vous choisissez pour les besoins de votre application). socket.INADDR_ANY vous donnera les données de multidiffusion, c'est vrai, et c'est plus simple si vous supposez un hôte à un seul domicile, mais je pense que c'est moins correct.
Mike S

@MikeS alors que je suis d'accord avec vous sur certains principes, l'idée d'utiliser des adresses IP pour sélectionner des interfaces est terriblement, terriblement lourde. Je connais bien le problème, mais dans un monde dynamique, l'adresse IP n'est pas la solution. Vous devez donc écrire du code qui itère tout et choisit par nom d'interface, regarde le nom de l'interface, sélectionne l'adresse IP actuelle et l'utilise. Espérons que l'adresse IP n'a pas changé entre-temps. J'aurais aimé que Linux / Unix ait normalisé l'utilisation des noms d'interface partout, et les langages de programmation l'avaient fait, cela rendrait un fichier de configuration plus raisonnable.
Brian Bulkowski

13

Meilleure utilisation:

sock.bind((MCAST_GRP, MCAST_PORT))

au lieu de:

sock.bind(('', MCAST_PORT))

car, si vous souhaitez écouter plusieurs groupes de multidiffusion sur le même port, vous recevrez tous les messages sur tous les écouteurs.


6

Afin de rejoindre un groupe de multidiffusion, Python utilise une interface de socket OS native. En raison de la portabilité et de la stabilité de l'environnement Python, de nombreuses options de socket sont directement transmises à l'appel natif de setsockopt. Le mode de fonctionnement de multidiffusion tel que rejoindre et abandonner l'appartenance à un groupe ne peut être accompli setsockoptque par .

Le programme de base pour recevoir un paquet IP multicast peut ressembler à:

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)

Premièrement, il crée un socket, le lie et déclenche la jonction du groupe de multidiffusion en émettant setsockopt. À la toute fin, il reçoit des paquets pour toujours.

L'envoi de trames IP multicast est simple. Si vous avez une seule carte réseau dans votre système, l'envoi de tels paquets ne diffère pas de l'envoi de trames UDP habituelles. Tout ce dont vous avez besoin est de définir simplement l'adresse IP de destination correcte dans la sendto()méthode.

J'ai remarqué que beaucoup d'exemples autour d'Internet fonctionnent par accident. Même sur la documentation officielle de Python. Le problème pour tous est d'utiliser struct.pack de manière incorrecte. Veuillez noter que l'exemple typique utilise 4slcomme format et qu'il n'est pas aligné avec la structure réelle de l'interface de socket du système d'exploitation.

Je vais essayer de décrire ce qui se passe sous le capot lors de l'exercice de l'appel setsockopt pour un objet socket python.

Python transmet l'appel de la méthode setsockopt à l'interface native du socket C. La documentation du socket Linux (voir man 7 ip) présente deux formes de ip_mreqnstructure pour l'option IP_ADD_MEMBERSHIP. La forme la plus courte est longue de 8 octets et la plus longue est de 12 octets. L'exemple ci-dessus génère un setsockoptappel de 8 octets où les quatre premiers octets définissent multicast_groupet les quatre seconds définissent interface_ip.


2

Jetez un œil à py-multicast . Le module réseau peut vérifier si une interface prend en charge la multidiffusion (au moins sous 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

Peut-être que les problèmes de ne pas voir IGMP ont été causés par une interface ne prenant pas en charge la multidiffusion?


2

Juste une autre réponse pour expliquer quelques points subtils dans le code des autres réponses:

  • socket.INADDR_ANY- (Modifié) Dans le contexte de IP_ADD_MEMBERSHIP, cela ne lie pas vraiment le socket à toutes les interfaces, mais choisit simplement l'interface par défaut où la multidiffusion est active (selon la table de routage)
  • Rejoindre un groupe de multidiffusion n'est pas la même chose que lier un socket à une adresse d'interface locale

voir Que signifie lier un socket de multidiffusion (UDP)? pour en savoir plus sur le fonctionnement de la multidiffusion

Récepteur 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)

exemple d'utilisation: (exécutez ce qui suit dans deux consoles et choisissez votre propre --iface (doit être identique à l'interface qui reçoit les données de multidiffusion))

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'

Expéditeur 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)

exemple d'utilisation: # supposons que le récepteur se lie à l'adresse de groupe de multidiffusion ci-dessous et que certains programmes demandent à rejoindre ce groupe. Et pour simplifier le cas, supposons que le destinataire et l'expéditeur sont sous le même sous-réseau

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

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


INADDR_ANY ne «choisit pas l'une des interfaces locales]».
Marquis of Lorne

0

Pour que le code client (de tolomea) fonctionne sous Solaris, vous devez transmettre la valeur ttl de l' IP_MULTICAST_TTLoption socket en tant que caractère non signé. Sinon, vous obtiendrez une erreur. Cela a fonctionné pour moi sur Solaris 10 et 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))

-1

La réponse de tolomea a fonctionné pour moi. Je l'ai aussi piraté dans 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.