Bagaimana Anda mengirim dan menerima multicast UDP dengan Python? Apakah ada perpustakaan standar untuk melakukannya?
88
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
sock.bind((MCAST_GRP, MCAST_PORT))
, kode Anda mungkin dan mungkin tidak berfungsi, mungkin tidak berfungsi ketika Anda memiliki banyakPengirim 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()
sumber
sock.bind((MCAST_GRP, MCAST_PORT))
Penggunaan yang lebih baik:
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.
sumber
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 dengan
setsockopt
.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 benar
sendto()
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 biasa
4sl
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 bentukip_mreqn
struktur untuk opsi IP_ADD_MEMBERSHIP. Bentuk terpendek adalah 8 byte dan yang lebih panjang adalah 12 byte. Contoh di atas menghasilkansetsockopt
panggilan 8 byte di mana empat byte pertama mendefinisikanmulticast_group
dan empat byte kedua mendefinisikaninterface_ip
.sumber
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?
sumber
Hanya jawaban lain untuk menjelaskan beberapa poin halus dalam kode jawaban lain:
socket.INADDR_ANY
- (Diedit) Dalam konteksIP_ADD_MEMBERSHIP
, ini tidak benar-benar mengikat soket ke semua antarmuka tetapi hanya memilih antarmuka default di mana multicast sudah aktif (menurut tabel perutean)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'
sumber
Untuk membuat kode klien (dari tolomea) berfungsi di Solaris, Anda harus meneruskan nilai ttl untuk
IP_MULTICAST_TTL
opsi 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))
sumber
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
tcpdump sangat sederhana
Dalam kode Anda, Anda memiliki:
while True: # For Python 3, change next line to "print(sock.recv(10240))"
Mengapa 10240 ?
sumber
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)
sumber