Python'da UDP çok noktaya yayınını nasıl gönderir ve alırsınız? Bunu yapmak için standart bir kütüphane var mı?
Yanıtlar:
Bu benim için çalışıyor:
Teslim almak
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)
Gönder
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))
Çalışmayan http://wiki.python.org/moin/UdpCommunication'daki örneklere dayanmaktadır .
Sistemim ... Linux 2.6.31-15-generic # 50-Ubuntu SMP Sal Kasım 10 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4
sock.bind((MCAST_GRP, MCAST_PORT))
, kodunuz çalışabilir ve çalışmayabilir, birden çok nics olduğunda çalışmayabilir
Çok noktaya yayın grubuna yayın yapan çok noktaya yayın göndericisi:
#!/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()
Çok noktaya yayın grubundan okuyan ve onaltılık verileri konsola yazdıran çok noktaya yayın alıcısı:
#!/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()
sock.bind((MCAST_GRP, MCAST_PORT))
Python, çoklu yayın grubuna katılmak için yerel işletim sistemi soket arabirimini kullanır. Python ortamının taşınabilirliği ve kararlılığı nedeniyle birçok soket seçeneği doğrudan yerel soket setsockopt çağrısına yönlendirilir. Grup üyeliğine katılma ve üyelikten çıkma gibi çok noktaya yayın modu setsockopt
yalnızca gerçekleştirilebilir .
Çok noktaya yayın IP paketini almak için temel program şöyle görünebilir:
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)
Öncelikle soket oluşturur, bağlar ve yayınlayarak multicast grup birleştirmesini tetikler setsockopt
. En sonunda sonsuza kadar paketleri alır.
Çok noktaya yayın IP çerçevelerinin gönderilmesi basittir. Sisteminizde tek bir NIC varsa, bu tür paketleri göndermek, normal UDP çerçeve gönderimlerinden farklı değildir. Tek yapmanız gereken, sendto()
yöntemde doğru hedef IP adresini ayarlamaktır .
Aslında internetteki birçok örneğin tesadüfen çalıştığını fark ettim. Resmi python belgelerinde bile. Hepsi için sorun struct.pack'i yanlış kullanıyor. Lütfen tipik örneğin 4sl
format olarak kullanıldığını ve gerçek işletim sistemi soket arayüz yapısıyla uyumlu olmadığını unutmayın.
Python soket nesnesi için setsockopt çağrısını uygularken kaputun altında neler olduğunu açıklamaya çalışacağım.
Python, setsockopt yöntem çağrısını yerel C soket arabirimine iletir. Linux soket belgeleri (bkz. man 7 ip
) ip_mreqn
IP_ADD_MEMBERSHIP seçeneği için iki yapı biçimi sunar. En kısa biçim, 8 bayt uzunluğunda ve daha uzun 12 bayt uzunluğundadır. Yukarıdaki örnek setsockopt
, ilk dört baytın tanımladığı multicast_group
ve ikinci dört baytın tanımladığı 8 baytlık çağrı üretir interface_ip
.
Py-multicast'a bir göz atın . Ağ modülü, bir arayüzün çoklu yayını destekleyip desteklemediğini kontrol edebilir (en azından Linux'ta).
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
Belki de IGMP'yi görmeme ile ilgili sorunlar, çok noktaya yayını desteklemeyen bir arabirimden kaynaklanıyor mu?
Diğer cevapların kodundaki bazı ince noktaları açıklamak için sadece başka bir cevap:
socket.INADDR_ANY
- (Düzenlendi) Bağlamında IP_ADD_MEMBERSHIP
, bu, soketi tüm arabirimlere gerçekten bağlamaz, yalnızca çok noktaya yayının açık olduğu varsayılan arabirimi seçin (yönlendirme tablosuna göre)bkz bir çok noktaya (UDP) soketi bağlamaya ne demek? çok noktaya yayının nasıl çalıştığı hakkında daha fazla bilgi için
Çok noktaya yayın alıcısı:
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)
örnek kullanım: (aşağıdakini iki konsolda çalıştırın ve kendi yüzeyinizi seçin (çok noktaya yayın verilerini alan arayüzle aynı olmalıdır))
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'
Çok noktaya yayın gönderen:
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)
örnek kullanım: # alıcının aşağıdaki çok noktaya yayın grubu adresine bağlandığını ve bazı programların bu gruba katılmak istediğini varsayalım. Vakayı basitleştirmek için, alıcının ve gönderenin aynı alt ağda olduğunu varsayın
python3 multicast_send.py --mcast-group '224.1.1.2'
python3 multicast_send.py --mcast-group '224.1.1.4'
İstemci kodunun (tolomea'dan) Solaris üzerinde çalışmasını sağlamak için IP_MULTICAST_TTL
soket seçeneğinin ttl değerini işaretsiz karakter olarak iletmeniz gerekir . Aksi takdirde bir hata alırsınız. Bu benim için Solaris 10 ve 11'de çalıştı:
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))
Bu örnek, belirsiz bir nedenle benim için çalışmıyor.
Anlaşılmaz değil, basit bir yönlendirme.
OpenBSD'de
route add -inet 224.0.0.0/4 224.0.0.1
Rotayı Linux'ta bir geliştiriciye ayarlayabilirsiniz
route add -net 224.0.0.0 netmask 240.0.0.0 dev wlp2s0
Linux'ta tüm çok noktaya yayın trafiğini tek bir arabirime zorla
ifconfig wlp2s0 allmulti
tcpdump süper basittir
tcpdump -n multicast
Kodunuzda şunlar var:
while True:
# For Python 3, change next line to "print(sock.recv(10240))"
Neden 10240 ?
çok noktaya yayın paket boyutu 1316 bayt olmalıdır
tolomea'nın cevabı benim için çalıştı. Socketserver.UDPServer'a da hackledim :
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)