web-dev-qa-db-fra.com

Comment UDP multicast en Python?

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

81
NoName

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:
  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)
sock.sendto("robot", (MCAST_GRP, MCAST_PORT))

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

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

88
Gordon Wrigley

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 __== '__main__':
  main()

Récepteur multidiffusion qui lit 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 __== '__main__':
  main()
17
Niranjan Tulpule

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 obtiendrez tous les messages sur tous les écouteurs.

12
st0ne

Pour rejoindre le groupe de multidiffusion Python utilise l’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 à setsockopt natif. Le mode de fonctionnement multicast, tel que l’adhésion et la suppression de l’appartenance à un groupe, ne peut être effectué que par setsockopt.

Le programme de base pour la réception de paquets IP en multidiffusion 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 de groupes de multidiffusion en émettant setsockopt. À la fin, il reçoit les paquets pour toujours.

L'envoi de trames IP en multidiffusion est simple. Si vous avez un seul NIC dans votre système, l'envoi de tels paquets ne diffère pas de l'envoi de trames UDP habituel. Il suffit de définir l'adresse IP de destination correcte dans sendto() méthode.

J'ai remarqué que beaucoup d'exemples autour d'Internet fonctionnent par accident en fait. Même sur la documentation officielle python. Le problème pour tous utilise mal struct.pack. Notez que l'exemple typique utilise 4sl en tant que format et il n'est pas aligné avec la structure réelle de l'interface de socket OS.

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

Python transmet l'appel de la méthode setsockopt à l'interface de socket C native. Documentation sur le socket Linux (voir man 7 ip) introduit deux formes de ip_mreqn structure de l’option IP_ADD_MEMBERSHIP. La forme la plus courte est de 8 octets et longue de 12 octets. L'exemple ci-dessus génère 8 octets setsockopt appel où poing pour octets définit multicast_group et deuxieme interface_ip.

4
Leszek Wojcik

Il existe un cadre pour faire cela à partir de http://twistedmatrix.com/trac/ . Voici l'exemple https://twistedmatrix.com/documents/12.2.0/core/howto/udp.html

2
dataq

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

voir --- (Que signifie lier une socket multicast (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 __== '__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: (lancez la commande ci-dessous dans deux consoles et choisissez votre propre --iface (doit être identique à l'interface recevant 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 __== '__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 destinataire se lie à l'adresse du 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 se trouvent 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'

2
pterodragon

Jetez un oeil à 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 des problèmes liés au fait de ne pas voir IGMP ont été causés par une interface ne prenant pas en charge la multidiffusion?

2
wroniasty

Pour que le code client (de tolomea) fonctionne sous Solaris, vous devez transmettre la valeur ttl de l'option de socket IP_MULTICAST_TTL 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))
0
bunnyrabbit