Bagaimana Anda UDP multicast dengan Python?

88

Bagaimana Anda mengirim dan menerima multicast UDP dengan Python? Apakah ada perpustakaan standar untuk melakukannya?

NoName
sumber

Jawaban:

101

Ini bekerja untuk saya:

Menerima

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)

Kirim

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

Ini didasarkan pada contoh dari http://wiki.python.org/moin/UdpCommunication yang tidak berfungsi.

Sistem saya adalah ... Linux 2.6.31-15-generik # 50-Ubuntu SMP Sel 10 Nov 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4

Gordon Wrigley
sumber
6
Untuk mac os x Anda perlu menggunakan opsi socket.SO_REUSEPORT sebagai alternatif untuk socket.SO_REUSEADDR pada contoh di atas, untuk mengizinkan beberapa pendengar pada kombinasi alamat port multicast yang sama.
atikat
Untuk mengirim, saya juga membutuhkan "sock.bind ((<local ip>, 0))" karena pendengar multicast saya terikat ke adaptor tertentu.
Mark Foreman
2
untuk multicast udp Anda perlu mengikat ke grup / port multicast bukan port grup lokal sock.bind((MCAST_GRP, MCAST_PORT)), kode Anda mungkin dan mungkin tidak berfungsi, mungkin tidak berfungsi ketika Anda memiliki banyak
nics
@atikat: Terima kasih !! Meskipun mengapa kita membutuhkan ini di MAC tetapi tidak di Ubuntu?
Kyuubi
2
@RandallCook: Ketika saya mengganti '' oleh MCAST_GRP saya mendapatkan socket.error: [Errno 10049] Alamat yang diminta tidak valid dalam konteksnya
stewbasic
17

Pengirim multicast yang menyiarkan ke grup 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()

Penerima multicast yang membaca dari grup multicast dan mencetak data hex ke konsol:

#!/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
sumber
Saya mencoba ini, tidak berhasil. Di Wireshark saya dapat melihat transmisi, tetapi saya tidak melihat IGMP bergabung dan saya tidak menerima apa pun.
Gordon Wrigley
1
Anda perlu mengikat ke grup / port multicast, bukan port lokal pada alamat multicast,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB
1
Contoh ini tidak berhasil untuk saya, karena alasan yang tidak jelas. Menggunakan socket.gethostbyname (socket.gethostname ()) untuk memilih antarmuka tidak selalu memilih antarmuka eksternal - pada kenyataannya, pada sistem debian, ia cenderung memilih alamat loopback. Debian menambahkan entri 127.0.1.1 di tabel host untuk nama host. Sebaliknya, akan lebih efektif untuk menggunakan socket.INADDR_ANY, yang digunakan oleh jawaban peringkat yang lebih tinggi melalui pernyataan 'paket' (yang lebih benar daripada '+'). Selain itu, penggunaan IP_MULTICAST_IF tidak diperlukan, karena jawaban peringkat yang lebih tinggi menyatakan dengan benar.
Brian Bulkowski
1
@BrianBulkowski ada banyak pemrogram yang menggunakan socket.INADDR_ANY, yang sangat menyedihkan dan cemas bagi kita yang memiliki banyak antarmuka, yang membutuhkan data multicast untuk datang pada antarmuka tertentu. Solusinya bukan socket.INADDR_ANY. Itu untuk memilih antarmuka yang tepat berdasarkan alamat IP, bagaimanapun menurut Anda yang terbaik (file konfigurasi, menanyakan pengguna akhir, bagaimanapun Anda memilih untuk kebutuhan aplikasi Anda). socket.INADDR_ANY akan memberi Anda data multicast, true, dan paling mudah jika Anda menganggap host single-homed, tetapi saya pikir itu kurang tepat.
Mike S
@MikeS sementara saya setuju dengan Anda dalam beberapa prinsip, gagasan menggunakan alamat IP untuk memilih antarmuka sangat, sangat penuh. Saya tahu masalahnya dengan baik, tetapi di dunia yang dinamis, dan alamat IP bukanlah jawabannya. Jadi, Anda perlu menulis kode yang mengulang semuanya dan memilih berdasarkan nama antarmuka, melihat nama antarmuka, memilih alamat IP saat ini, dan menggunakannya. Semoga alamat IP tidak berubah untuk sementara. Saya berharap Linux / Unix memiliki standar penggunaan nama antarmuka di mana-mana, dan bahasa pemrogramannya, yang akan membuat file konfigurasi lebih masuk akal.
Brian Bulkowski
13

Penggunaan yang lebih baik:

sock.bind((MCAST_GRP, MCAST_PORT))

dari pada:

sock.bind(('', MCAST_PORT))

karena, jika Anda ingin mendengarkan beberapa grup multicast di port yang sama, Anda akan mendapatkan semua pesan di semua pendengar.

st0ne
sumber
6

Untuk Bergabung dengan grup multicast, Python menggunakan antarmuka soket OS asli. Karena portabilitas dan stabilitas lingkungan Python, banyak opsi soket secara langsung diteruskan ke panggilan setockopt soket asli. Mode operasi multicast seperti bergabung dan membatalkan keanggotaan grup dapat dilakukan dengansetsockopt .

Program dasar untuk menerima paket IP multicast dapat berupa:

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)

Pertama, ia membuat soket, mengikatnya dan memicu grup multicast bergabung dengan mengeluarkan setsockopt . Pada akhirnya, ia menerima paket selamanya.

Mengirim frame IP multicast sangatlah mudah. Jika Anda memiliki NIC tunggal dalam sistem Anda, pengiriman paket tersebut tidak berbeda dari pengiriman frame UDP biasa. Yang perlu Anda perhatikan hanyalah mengatur alamat IP tujuan yang benarsendto() metode.

Saya perhatikan bahwa banyak contoh seputar Internet bekerja secara tidak sengaja. Bahkan pada dokumentasi resmi python. Masalah untuk mereka semua adalah menggunakan struct.pack secara tidak benar. Harap diperhatikan bahwa penggunaan contoh biasa4sl sebagai format dan tidak selaras dengan struktur antarmuka soket OS yang sebenarnya.

Saya akan mencoba menjelaskan apa yang terjadi di bawah kap saat menjalankan panggilan setsockopt untuk objek soket python.

Python meneruskan panggilan metode setsockopt ke antarmuka soket C asli. Dokumentasi soket Linux (lihat man 7 ip) memperkenalkan dua bentuk ip_mreqnstruktur untuk opsi IP_ADD_MEMBERSHIP. Bentuk terpendek adalah 8 byte dan yang lebih panjang adalah 12 byte. Contoh di atas menghasilkan setsockoptpanggilan 8 byte di mana empat byte pertama mendefinisikan multicast_groupdan empat byte kedua mendefinisikan interface_ip.

Leszek Wojcik
sumber
2

Lihat py-multicast . Modul jaringan dapat memeriksa apakah suatu antarmuka mendukung multicast (setidaknya di 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

Mungkin masalah tidak melihat IGMP, disebabkan oleh antarmuka yang tidak mendukung multicast?

wroniasty
sumber
2

Hanya jawaban lain untuk menjelaskan beberapa poin halus dalam kode jawaban lain:

  • socket.INADDR_ANY - (Diedit) Dalam konteks IP_ADD_MEMBERSHIP , ini tidak benar-benar mengikat soket ke semua antarmuka tetapi hanya memilih antarmuka default di mana multicast sudah aktif (menurut tabel perutean)
  • Bergabung dengan grup multicast tidak sama dengan mengikat soket ke alamat antarmuka lokal

lihat Apa artinya mengikat soket multicast (UDP)?untuk mengetahui lebih lanjut tentang cara kerja multicast

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

contoh penggunaan: (jalankan di bawah ini dalam dua konsol dan pilih sendiri --iface (harus sama dengan antarmuka yang menerima data 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'

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

contoh penggunaan: # anggap penerima mengikat ke alamat grup multicast di bawah ini dan bahwa beberapa program meminta untuk bergabung dengan grup itu. Dan untuk menyederhanakan kasus, asumsikan penerima dan pengirim berada di bawah subnet yang sama

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

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

pterodragon
sumber
INADDR_ANY tidak 'memilih salah satu antarmuka lokal]'.
pengguna207421
0

Untuk membuat kode klien (dari tolomea) berfungsi di Solaris, Anda harus meneruskan nilai ttl untuk IP_MULTICAST_TTLopsi socket sebagai unsigned char. Jika tidak, Anda akan mendapatkan error. Ini berhasil untuk saya di Solaris 10 dan 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))
kelinci
sumber
0

Contoh ini tidak berhasil untuk saya, karena alasan yang tidak jelas.

Tidak jelas, ini perutean sederhana.

Di OpenBSD

route add -inet 224.0.0.0/4 224.0.0.1

Anda dapat mengatur rute ke dev di Linux

route add -net 224.0.0.0 netmask 240.0.0.0 dev wlp2s0

memaksa semua lalu lintas multicast ke satu antarmuka di Linux

   ifconfig wlp2s0 allmulti

tcpdump sangat sederhana

tcpdump -n multicast

Dalam kode Anda, Anda memiliki:

while True:
  # For Python 3, change next line to "print(sock.recv(10240))"

Mengapa 10240 ?

ukuran paket multicast harus 1316 byte

Skandal Leroy
sumber
-1

jawaban tolomea berhasil untukku. Saya meretasnya ke socketserver.UDPServer juga:

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
sumber