¿Cómo UDP multicast en Python?

¿Cómo se envía y recibe la multidifusión UDP en Python? ¿Hay una biblioteca estándar para hacerlo?

Esto funciona para mí:

Recibir

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) 

Enviar

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

Se basa en los ejemplos de http://wiki.python.org/moin/UdpCommunication que no funcionaron.

Mi sistema es … Linux 2.6.31-15-genérico # 50-Ubuntu SMP mar 10 de noviembre 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4

Remitente de multidifusión que transmite a un grupo de multidifusión:

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

Receptor de multidifusión que lee de un grupo de multidifusión e imprime datos hexadecimales en la consola:

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

Mejor uso:

 sock.bind((MCAST_GRP, MCAST_PORT)) 

en lugar de:

 sock.bind(('', MCAST_PORT)) 

porque, si desea escuchar varios grupos de multidifusión en el mismo puerto, obtendrá todos los mensajes en todos los oyentes.

Para unirse al grupo de multidifusión, Python utiliza una interfaz de socket de SO nativa. Debido a la portabilidad y estabilidad del entorno Python, muchas de las opciones de socket se reenvían directamente a la llamada nativa setsockopt. El modo de operación de multidifusión, como unirse y setsockopt pertenencia a un grupo, se puede realizar solo con setsockopt .

El progtwig básico para recibir paquetes IP de multidifusión puede tener el siguiente aspecto:

 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) 

En primer lugar, crea socket, lo enlaza y desencadena la setsockopt grupos de multidifusión mediante la emisión de setsockopt . Al final recibe paquetes para siempre.

Enviar ttwigs IP de multidifusión es sencillo. Si tiene una NIC única en su sistema, el envío de dichos paquetes no difiere del envío habitual de ttwigs UDP. Todo lo que debe tener en cuenta es configurar la dirección IP de destino correcta en el método sendto() .

Noté que muchos ejemplos alrededor de Internet funcionan por accidente, de hecho. Incluso en la documentación oficial de python. Problema para todos ellos están utilizando struct.pack incorrectamente. Tenga en cuenta que el ejemplo típico utiliza 4sl como formato y no está alineado con la estructura de interfaz de socket del sistema operativo real.

Trataré de describir lo que sucede debajo de la campana cuando se ejerce el llamado setsockopt para el objeto de socket Python.

Python reenvía la llamada del método setsockopt a la interfaz de socket C nativa. La documentación de socket de Linux (ver man 7 ip ) introduce dos formas de estructura ip_mreqn para la opción IP_ADD_MEMBERSHIP. La forma más corta es de 8 bytes y una longitud de 12 bytes. El ejemplo anterior genera una llamada setsockopt 8 bytes en la que fist for bytes define multicast_group y second interface_ip .

Echa un vistazo a py-multicast . El módulo de red puede verificar si una interfaz admite multidifusión (al menos en 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 

¿Quizás los problemas por no ver IGMP fueron causados ​​por una interfaz que no admite multidifusión?

Hay un marco para hacer esto desde http://twistedmatrix.com/trac/ . Aquí está el ejemplo https://twistedmatrix.com/documents/12.2.0/core/howto/udp.html

Solo otra respuesta para explicar algunos puntos sutiles en el código de las otras respuestas:

  • socket.INADDR_ANY : esto realmente no se enlaza con todas las interfaces, solo elige una de la interfaz local
  • Unirse a un grupo de multidifusión no es lo mismo que vincular un socket a una dirección de interfaz local

ver ¿Qué significa enlazar un socket de multidifusión (UDP)? para más información sobre cómo funciona la multidifusión

Receptor de multidifusión:

 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) 

Ejemplo de uso: (ejecute lo siguiente en dos consolas y elija su propio –iface (debe ser el mismo que la interfaz que recibe los datos de multidifusión))

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'

Remitente de multidifusión:

 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) 

ejemplo de uso: # asume que el receptor se enlaza a la siguiente dirección de grupo de multidifusión y que algunos progtwigs solicitan unirse a ese grupo. Y para simplificar el caso, suponga que el receptor y el remitente están bajo la misma subred

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

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

Para hacer que el código de cliente (de tolomea) funcione en Solaris, debe pasar el valor ttl para la opción de socket IP_MULTICAST_TTL como un char sin firma. De lo contrario obtendrá un error. Esto me funcionó en Solaris 10 y 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 respuesta de tolomea funcionó para mí. Lo hackeé en socketserver.UDPServer también:

 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) 

El tráfico de multidifusión no es diferente del UDP normal, excepto por la dirección IP. Echa un vistazo a la biblioteca de socket estándar. Es posible que pueda encontrar algo que se base en el socket y que sea más fácil de usar.