Como você envia e recebe multicast UDP em Python? Existe uma biblioteca padrão para fazer isso?
86
Isso funciona para mim:
Receber
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)
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)
# 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))
É baseado nos exemplos de http://wiki.python.org/moin/UdpCommunication que não funcionou.
Meu sistema é ... Linux 2.6.31-15-genérico # 50-Ubuntu SMP Tue Nov 10 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4
sock.bind((MCAST_GRP, MCAST_PORT))
, seu código pode ou não funcionar, pode não funcionar quando você tem vários nicsRemetente multicast que transmite para um grupo multicast:
#!/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 multicast que lê de um grupo multicast e imprime dados hexadecimais no 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()
fonte
sock.bind((MCAST_GRP, MCAST_PORT))
Melhor uso:
ao invés de:
sock.bind(('', MCAST_PORT))
porque, se você quiser ouvir vários grupos multicast na mesma porta, receberá todas as mensagens em todos os ouvintes.
fonte
Para ingressar no grupo multicast, o Python usa a interface de soquete do sistema operacional nativo. Devido à portabilidade e estabilidade do ambiente Python, muitas das opções de soquete são encaminhadas diretamente para a chamada setsockopt de soquete nativo. O modo multicast de operação, como ingressar e cancelar a associação a um grupo, pode ser realizado
setsockopt
apenas porO programa básico para receber pacotes IP multicast pode ser semelhante a:
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)
Em primeiro lugar, ele cria socket, vincula-o e dispara a junção de grupos multicast por emissão
setsockopt
. No final, ele recebe pacotes para sempre.O envio de quadros IP multicast é direto. Se você tiver uma única NIC em seu sistema, o envio desses pacotes não difere do envio de quadros UDP normais. Tudo que você precisa fazer é definir o endereço IP de destino correto no
sendto()
método.Percebi que muitos exemplos em torno da Internet funcionam por acaso. Mesmo na documentação oficial do python. O problema para todos eles é o uso incorreto de struct.pack. Informamos que o exemplo típico usa
4sl
como formato e não está alinhado com a estrutura real da interface de soquete do SO.Vou tentar descrever o que acontece por baixo do capô ao exercitar a chamada setsockopt para o objeto socket python.
O Python encaminha a chamada do método setsockopt para a interface de soquete C nativa. A documentação do soquete Linux (consulte Recursos
man 7 ip
) apresenta duas formas deip_mreqn
estrutura para a opção IP_ADD_MEMBERSHIP. O formato mais curto tem 8 bytes e o mais longo tem 12 bytes. O exemplo acima gera umasetsockopt
chamada de 8 bytes onde os primeiros quatro bytes definemmulticast_group
e os segundos quatro bytes defineminterface_ip
.fonte
Dê uma olhada em py-multicast . O módulo de rede pode verificar se uma interface suporta multicast (pelo menos no 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
Talvez os problemas em não ver o IGMP tenham sido causados por uma interface que não suporta multicast?
fonte
Apenas outra resposta para explicar alguns pontos sutis no código das outras respostas:
socket.INADDR_ANY
- (Editado) No contexto deIP_ADD_MEMBERSHIP
, isso realmente não liga o soquete a todas as interfaces, mas apenas escolhe a interface padrão onde o multicast está ativo (de acordo com a tabela de roteamento)consulte O que significa vincular um soquete multicast (UDP)? para saber mais sobre como funciona o multicast
Receptor 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)
exemplo de uso: (execute o seguinte em dois consoles e escolha seu próprio --iface (deve ser igual à interface que recebe os dados multicast))
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'
Remetente 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)
exemplo de uso: # assume que o receptor se liga ao endereço de grupo multicast abaixo e que algum programa solicita a adesão a esse grupo. E para simplificar o caso, suponha que o destinatário e o remetente estejam na mesma sub-rede
python3 multicast_send.py --mcast-group '224.1.1.2'
python3 multicast_send.py --mcast-group '224.1.1.4'
fonte
Para fazer o código do cliente (de tolomea) funcionar no Solaris, você precisa passar o valor ttl para a
IP_MULTICAST_TTL
opção de soquete como um caractere não assinado. Caso contrário, você obterá um erro. Isso funcionou para mim no Solaris 10 e 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))
fonte
a resposta de tolomea funcionou para mim. Eu o hackeei em socketserver.UDPServer também:
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)
fonte