Como você faz multicast UDP em Python?

86

Como você envia e recebe multicast UDP em Python? Existe uma biblioteca padrão para fazer isso?

NoName
fonte

Respostas:

98

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

Gordon Wrigley
fonte
6
Para mac os x, você precisa usar a opção socket.SO_REUSEPORT como alternativa a socket.SO_REUSEADDR no exemplo acima, para permitir vários ouvintes na mesma combinação de endereço de porta multicast.
atikat
Para o envio, também precisei de "sock.bind ((<local ip>, 0))" porque meu ouvinte multicast estava vinculado a um adaptador específico.
Mark Foreman
2
para udp multicast, você precisa se ligar ao grupo / porta multicast, não à porta do grupo local sock.bind((MCAST_GRP, MCAST_PORT)), seu código pode ou não funcionar, pode não funcionar quando você tem vários nics
stefanB
@atikat: Obrigado !! Embora por que precisamos disso no MAC, mas não no Ubuntu?
Kyuubi
2
@RandallCook: Quando eu substituo '' por MCAST_GRP eu obtenho socket.error: [Errno 10049] O endereço solicitado não é válido em seu contexto
stewbasic
17

Remetente 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()
Niranjan Tulpule
fonte
Eu tentei isso, não deu certo. No Wireshark posso ver a transmissão, mas não vejo nenhum material de junção de IGMP e não recebo nada.
Gordon Wrigley
1
você precisa se ligar ao grupo / porta multicast, não à porta local no endereço multicast,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB
1
Este exemplo não funciona para mim, por um motivo obscuro. Usar socket.gethostbyname (socket.gethostname ()) para selecionar a interface nem sempre elege a interface externa - de fato, em sistemas debian, ele tende a selecionar o endereço de loopback. O Debian adiciona uma entrada de 127.0.1.1 na tabela de host para o nome do host. Em vez disso, é mais eficaz usar socket.INADDR_ANY, que a resposta de classificação mais alta usa por meio da declaração 'pack' (que é mais correta do que '+'). Além disso, o uso de IP_MULTICAST_IF não é obrigatório, como afirma corretamente a resposta de classificação superior.
Brian Bulkowski
1
@BrianBulkowski existem muitos programadores que usam socket.INADDR_ANY, para grande desgosto e consternação daqueles de nós com múltiplas interfaces, que precisam dos dados multicast para vir em uma interface particular. A solução não é socket.INADDR_ANY. É para selecionar a interface adequada pelo endereço IP, como você achar melhor (um arquivo de configuração, perguntando ao usuário final, da maneira que você escolher para as necessidades do seu aplicativo). socket.INADDR_ANY obterá os dados multicast, verdade, e é mais fácil se você assumir um host single-homed, mas acho que é menos correto.
Mike S
@MikeS Embora eu concorde com você em alguns princípios, a ideia de usar endereços IP para selecionar interfaces é terrivelmente, terrivelmente carregada. Eu conheço bem o problema, mas em um mundo dinâmico, o endereço IP não é a resposta. Portanto, você precisa escrever um código que itere tudo e escolha pelo nome da interface, veja o nome da interface, escolha o endereço IP atual e use-o. Felizmente, o endereço IP não mudou nesse meio tempo. Eu gostaria que o Linux / Unix tivesse padronizado o uso de nomes de interface em todos os lugares, e as linguagens de programação tivessem, isso tornaria um arquivo de configuração mais prático.
Brian Bulkowski
13

Melhor uso:

sock.bind((MCAST_GRP, MCAST_PORT))

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.

st0ne
fonte
6

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 setsockoptapenas por

O 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 4slcomo 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 de ip_mreqnestrutura 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 uma setsockoptchamada de 8 bytes onde os primeiros quatro bytes definem multicast_groupe os segundos quatro bytes definem interface_ip.

Leszek Wojcik
fonte
2

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?

wroniasty
fonte
2

Apenas outra resposta para explicar alguns pontos sutis no código das outras respostas:

  • socket.INADDR_ANY- (Editado) No contexto de IP_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)
  • Aderir a um grupo multicast não é o mesmo que ligar um socket a um endereço de interface local

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'

2 rotações
fonte
INADDR_ANY não 'escolhe uma das interfaces locais]'.
Marquês de Lorne
0

Para fazer o código do cliente (de tolomea) funcionar no Solaris, você precisa passar o valor ttl para a IP_MULTICAST_TTLopçã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))
coelhinho
fonte
-1

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