Comment envoyer et recevoir la multidiffusion UDP en Python? Existe-t-il une bibliothèque standard pour le faire?
Réponses:
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
sock.bind((MCAST_GRP, MCAST_PORT))
, votre code peut et peut ne pas fonctionner, il peut ne pas fonctionner lorsque vous avez plusieurs
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()
sock.bind((MCAST_GRP, MCAST_PORT))
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 setsockopt
que 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 4sl
comme 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_mreqn
structure 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 setsockopt
appel de 8 octets où les quatre premiers octets définissent multicast_group
et les quatre seconds définissent interface_ip
.
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?
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)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'
Pour que le code client (de tolomea) fonctionne sous Solaris, vous devez transmettre la valeur ttl de l' IP_MULTICAST_TTL
option 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))
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)