Python'da çok noktaya yayın UDP nasıl yapılır?


88

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:


101

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


6
Mac os x için, aynı çok noktaya yayın bağlantı noktası adres kombinasyonunda birden çok dinleyiciye izin vermek için, yukarıdaki örnekte socket.SO_REUSEPORT seçeneğine alternatif olarak socket.SO_REUSEPORT seçeneğini kullanmanız gerekir.
atikat

Göndermek için ayrıca "sock.bind ((<local ip>, 0))" ihtiyacım vardı çünkü çok noktaya yayın dinleyicim belirli bir adaptöre bağlıydı.
Mark Foreman

2
udp çok noktaya yayın için yerel grup bağlantı noktasına değil, çok noktaya yayın grubuna / bağlantı noktasına bağlanmanız gerekir sock.bind((MCAST_GRP, MCAST_PORT)), kodunuz çalışabilir ve çalışmayabilir, birden çok nics olduğunda çalışmayabilir
stefanB

@atikat: Teşekkürler !! Buna neden MAC'da ihtiyacımız var ama Ubuntu'da değil?
Kyuubi

2
@RandallCook: MCAST_GRP ile '' değiştirdiğimde socket.error alıyorum: [Errno 10049] İstenen adres kendi bağlamında geçerli değil
stewbasic

17

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

Bunu denedim, işe yaramadı. Wireshark'ta iletimi görebiliyorum, ancak herhangi bir IGMP birleştirme öğesi görmüyorum ve hiçbir şey almıyorum.
Gordon Wrigley

1
çok noktaya yayın adresindeki yerel bağlantı noktasına değil, çok noktaya yayın grubuna / bağlantı noktasına bağlanmanız gerekir,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
Bu örnek, belirsiz bir nedenle benim için çalışmıyor. Arabirimi seçmek için socket.gethostbyname (socket.gethostname ()) kullanmak her zaman harici arabirimi seçmez - aslında debian sistemlerinde, geri döngü adresini seçme eğilimindedir. Debian, ana bilgisayar adı için ana bilgisayar tablosuna 127.0.1.1 girişi ekler. Bunun yerine, daha yüksek dereceli yanıtın 'pack' ifadesiyle ('+' ifadesinden daha doğru olan) kullandığı socket.INADDR_ANY'yi kullanmak daha etkilidir. Ayrıca, daha yüksek dereceli yanıt doğru şekilde belirttiği için IP_MULTICAST_IF kullanımı gerekli değildir.
Brian Bulkowski

1
@BrianBulkowski, belirli bir arayüzde çok noktaya yayın verisine ihtiyaç duyan çoklu arayüzlere sahip bizlerin büyük üzüntüsüne ve dehşetine soket.INADDR_ANY kullanan birçok programcı var. Çözüm socket.INADDR_ANY değil. IP adresine göre uygun arabirimi seçmektir, ancak siz bunun en iyisi olduğunu düşünürsünüz (bir yapılandırma dosyası, son kullanıcıya sorar, ancak uygulamanızın ihtiyaçlarını siz seçersiniz). socket.INADDR_ANY size çok noktaya yayın verilerini verecektir, doğru ve tek bağlantılı bir ana bilgisayar varsayarsanız en kolay olanıdır, ancak bunun daha az doğru olduğunu düşünüyorum.
Mike S

@MikeS, bazı prensiplerde sizinle aynı fikirde olsam da, arayüzleri seçmek için IP adreslerini kullanma fikri korkunç, korkunç derecede rahatsız. Sorunu iyi biliyorum, ancak dinamik bir dünyada ve IP adresi çözüm değil. Bu yüzden, her şeyi yineleyen ve arayüz adına göre seçen, arayüz adına bakan, mevcut IP adresini seçen ve bunu kullanan bir kod yazmanız gerekir. Umarım IP adresi bu arada değişmemiştir. Keşke Linux / Unix her yerde arayüz adlarını kullanma konusunda standartlaşmış olsaydı ve programlama dilleri bir yapılandırma dosyasını daha mantıklı hale getirecekti.
Brian Bulkowski

13

Daha iyi kullanım:

sock.bind((MCAST_GRP, MCAST_PORT))

onun yerine:

sock.bind(('', MCAST_PORT))

çünkü aynı bağlantı noktasında birden çok çok noktaya yayın grubunu dinlemek isterseniz, tüm dinleyicilerdeki tüm mesajları alırsınız.


6

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 setsockoptyalnı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 4slformat 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_mreqnIP_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_groupve ikinci dört baytın tanımladığı 8 baytlık çağrı üretir interface_ip.


2

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?


2

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)
  • Çok noktaya yayın grubuna katılmak, bir soketi yerel bir arayüz adresine bağlamakla aynı şey değildir

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'


INADDR_ANY yok değil '] Yerel arayüzleri birini seçin'.
user207421

0

İstemci kodunun (tolomea'dan) Solaris üzerinde çalışmasını sağlamak için IP_MULTICAST_TTLsoket 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))

0

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


-1

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.