Comment envoyer et recevoir une multidiffusion UDP en Python? Existe-t-il une bibliothèque standard pour le faire?
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
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()
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.
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
.
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
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 il suffit de choisir l’interface par défaut où la multidiffusion est active (selon la table de routage)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'
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?
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))