คุณทำ UDP multicast ใน Python ได้อย่างไร?


88

คุณส่งและรับ UDP multicast ใน Python ได้อย่างไร? มีห้องสมุดมาตรฐานให้ทำหรือไม่?

คำตอบ:


101

สิ่งนี้ใช้ได้กับฉัน:

รับ

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)

ส่ง

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

เป็นไปตามตัวอย่างจากhttp://wiki.python.org/moin/UdpCommunicationซึ่งไม่ได้ผล

ระบบของฉันคือ ... Linux 2.6.31-15-generic # 50-Ubuntu SMP อังคาร 10 พ.ย. 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
สำหรับ mac os x คุณต้องใช้ซ็อกเก็ตตัวเลือก SO_REUSEPORT เป็นทางเลือกแทนซ็อกเก็ต SO_REUSEADDR ในตัวอย่างข้างต้นเพื่ออนุญาตให้ผู้ฟังหลายคนใช้การรวมกันของที่อยู่พอร์ตแบบหลายผู้รับเดียวกัน
atikat

สำหรับการส่งฉันต้องใช้ "sock.bind ((<local ip>, 0))" ด้วยเพราะตัวฟังมัลติคาสต์ของฉันถูกผูกไว้กับอะแดปเตอร์เฉพาะ
Mark Foreman

2
สำหรับ udp multicast คุณต้องเชื่อมโยงกับกลุ่ม / พอร์ตแบบหลายผู้รับไม่ใช่พอร์ตกลุ่มภายในsock.bind((MCAST_GRP, MCAST_PORT))รหัสของคุณอาจใช้งานไม่ได้และอาจไม่ทำงานเมื่อคุณมีหลาย
nics

@atikat: ขอบคุณ !! แม้ว่าทำไมเราถึงต้องการสิ่งนี้บน MAC แต่ไม่ใช่บน Ubuntu?
Kyuubi

2
@RandallCook: เมื่อฉันแทนที่ '' โดย MCAST_GRP ฉันได้รับ socket.error: [Errno 10049] ที่อยู่ที่ร้องขอไม่ถูกต้องในบริบทของมัน
stewbasic

17

ผู้ส่งแบบหลายผู้รับที่กระจายสัญญาณไปยังกลุ่มมัลติคาสต์:

#!/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()

ตัวรับมัลติคาสต์ที่อ่านจากกลุ่มมัลติคาสต์และพิมพ์ข้อมูลฐานสิบหกไปยังคอนโซล:

#!/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()

ฉันลองแล้วมันไม่ได้ผล ใน Wireshark ฉันเห็นการส่งข้อมูล แต่ฉันไม่เห็นสิ่งที่เข้าร่วม IGMP และฉันไม่ได้รับอะไรเลย
Gordon Wrigley

1
คุณต้องเชื่อมโยงกับกลุ่ม / พอร์ตแบบหลายผู้รับไม่ใช่พอร์ตภายในที่อยู่แบบหลายผู้รับsock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
ตัวอย่างนี้ไม่ได้ผลสำหรับฉันด้วยเหตุผลที่คลุมเครือ การใช้ socket.gethostbyname (socket.gethostname ()) เพื่อเลือกอินเทอร์เฟซไม่ได้เลือกอินเทอร์เฟซภายนอกเสมอไป - ในระบบเดเบียนมีแนวโน้มที่จะเลือกที่อยู่ลูปแบ็ค Debian เพิ่มรายการ 127.0.1.1 ในตารางโฮสต์สำหรับชื่อโฮสต์ แต่การใช้ socket.INADDR_ANY จะมีประสิทธิภาพมากกว่าซึ่งคำตอบอันดับที่สูงกว่าจะใช้ผ่านคำสั่ง 'pack' (ซึ่งถูกต้องมากกว่า '+') นอกจากนี้ไม่จำเป็นต้องใช้ IP_MULTICAST_IF เนื่องจากคำตอบอันดับที่สูงกว่าระบุอย่างถูกต้อง
Brian Bulkowski

1
@BrianBulkowski มีโปรแกรมเมอร์หลายคนที่ใช้ socket.INADDR_ANY เพื่อความวิบัติและความหวาดกลัวอันยิ่งใหญ่ของพวกเราที่มีอินเทอร์เฟซที่หลากหลายซึ่งต้องการข้อมูลมัลติคาสต์ที่จะมาในอินเทอร์เฟซเฉพาะ โซลูชันไม่ใช่ซ็อกเก็ต INADDR_ANY คือการเลือกอินเทอร์เฟซที่เหมาะสมตามที่อยู่ IP อย่างไรก็ตามคุณคิดว่าดีที่สุด (ไฟล์กำหนดค่าโดยถามผู้ใช้ปลายทางว่าคุณเลือกตามความต้องการของแอปพลิเคชันของคุณอย่างไร) socket.INADDR_ANY จะทำให้คุณได้รับข้อมูลมัลติคาสต์เป็นจริงและง่ายที่สุดถ้าคุณสมมติว่าเป็นโฮสต์แบบ homed เดียว แต่ฉันคิดว่ามันถูกต้องน้อยกว่า
Mike S

@MikeS ในขณะที่ฉันเห็นด้วยกับคุณในหลักการบางประการความคิดในการใช้ที่อยู่ IP เพื่อเลือกอินเทอร์เฟซนั้นค่อนข้างแย่มาก ฉันรู้ปัญหาดี แต่ในโลกที่ไม่หยุดนิ่งและที่อยู่ IP ไม่ใช่คำตอบ ดังนั้นคุณต้องเขียนโค้ดที่วนซ้ำทุกอย่างและเลือกตามชื่ออินเทอร์เฟซดูชื่ออินเทอร์เฟซเลือกที่อยู่ IP ปัจจุบันและใช้สิ่งนั้น หวังว่าที่อยู่ IP จะไม่เปลี่ยนแปลงในระหว่างนี้ ฉันหวังว่า Linux / Unix มีมาตรฐานในการใช้ชื่ออินเทอร์เฟซทุกที่และภาษาการเขียนโปรแกรมมีซึ่งจะทำให้ไฟล์กำหนดค่าเหมาะสมยิ่งขึ้น
Brian Bulkowski

13

ใช้ดีกว่า:

sock.bind((MCAST_GRP, MCAST_PORT))

แทน:

sock.bind(('', MCAST_PORT))

เพราะหากคุณต้องการฟังกลุ่มมัลติคาสต์หลายกลุ่มในพอร์ตเดียวกันคุณจะได้รับข้อความทั้งหมดจากผู้ฟังทั้งหมด


6

ในการเข้าร่วมกลุ่มหลายผู้รับ Python ใช้อินเทอร์เฟซซ็อกเก็ต OS ดั้งเดิม เนื่องจากความสามารถในการพกพาและความเสถียรของสภาพแวดล้อม Python ตัวเลือกซ็อกเก็ตจำนวนมากจึงถูกส่งต่อโดยตรงไปยังการเรียก setockopt ของซ็อกเก็ตดั้งเดิม โหมดการทำงานหลายผู้รับเช่นการเข้าร่วมและการเลิกเป็นสมาชิกกลุ่มสามารถทำได้โดยsetsockoptเท่านั้น

โปรแกรมพื้นฐานสำหรับการรับแพ็กเก็ต IP แบบหลายผู้รับอาจมีลักษณะดังนี้:

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)

ประการแรกมันสร้างซ็อกเก็ตผูกมันและทริกเกอร์ทริกเกอร์การเข้าร่วมกลุ่มมัลติคาสต์โดยการออก setsockoptประการแรกมันจะสร้างซ็อกเก็ตผูกมันและทริกเกอร์ทริกเกอร์กลุ่มหลายผู้รับการเข้าร่วมโดยการออกท้ายที่สุดจะได้รับแพ็กเก็ตตลอดไป

การส่งเฟรม IP แบบมัลติคาสต์นั้นตรงไปตรงมา หากคุณมี NIC เดียวในระบบของคุณการส่งแพ็กเก็ตดังกล่าวไม่แตกต่างจากการส่งเฟรม UDP ตามปกติ สิ่งที่คุณต้องดูแลมีเพียงตั้งค่าที่อยู่ IP ปลายทางที่ถูกต้องในsendto()วิธีการ

ฉันสังเกตเห็นว่าตัวอย่างมากมายในอินเทอร์เน็ตใช้งานได้โดยบังเอิญ แม้แต่ในเอกสาร Python อย่างเป็นทางการ ปัญหาสำหรับพวกเขาทั้งหมดใช้ struct.pack ไม่ถูกต้อง โปรดทราบว่าตัวอย่างทั่วไปใช้4slเป็นรูปแบบและไม่สอดคล้องกับโครงสร้างอินเทอร์เฟซซ็อกเก็ตระบบปฏิบัติการจริง

ฉันจะพยายามอธิบายสิ่งที่เกิดขึ้นภายใต้ประทุนเมื่อออกกำลังกาย setsockopt เรียกหา python socket object

Python ส่งต่อเมธอด setsockopt เรียกใช้อินเทอร์เฟซซ็อกเก็ต C ดั้งเดิม เอกสารเกี่ยวกับซ็อกเก็ต Linux (ดูman 7 ip) แนะนำip_mreqnโครงสร้างสองรูปแบบสำหรับอ็อพชัน IP_ADD_MEMBERSHIP แบบสั้นที่สุดคือ 8 ไบต์ยาวและยาวกว่า 12 ไบต์ ตัวอย่างข้างต้นสร้าง 8 ไบต์setsockoptโทรที่สี่ไบต์แรกกำหนดและครั้งที่สองสี่ไบต์กำหนดmulticast_groupinterface_ip


2

มีลักษณะที่PY-หลายผู้รับ โมดูลเครือข่ายสามารถตรวจสอบว่าอินเทอร์เฟซรองรับมัลติคาสต์หรือไม่ (อย่างน้อยบน 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

บางทีปัญหาที่ไม่เห็น IGMP เกิดจากอินเทอร์เฟซไม่รองรับมัลติคาสต์?


2

คำตอบอื่นเพื่ออธิบายประเด็นที่ละเอียดอ่อนในรหัสของคำตอบอื่น ๆ :

  • socket.INADDR_ANY - (แก้ไข) ในบริบทของ IP_ADD_MEMBERSHIPนี้ไม่ได้ผูกซ็อกเก็ตเข้ากับอินเทอร์เฟซทั้งหมด แต่เพียงแค่เลือกอินเทอร์เฟซเริ่มต้นที่มัลติคาสต์อยู่ (ตามตารางเส้นทาง)
  • การเข้าร่วมกลุ่มมัลติคาสต์ไม่เหมือนกับการผูกซ็อกเก็ตกับที่อยู่อินเทอร์เฟซภายใน

ดูการผูกซ็อกเก็ตมัลติคาสต์ (UDP) หมายความว่าอย่างไรสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการทำงานของมัลติคาสต์

เครื่องรับมัลติคาสต์:

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)

ตัวอย่างการใช้งาน: (เรียกใช้ด้านล่างในสองคอนโซลและเลือก --iface ของคุณเอง (ต้องเหมือนกับอินเทอร์เฟซที่รับข้อมูลแบบหลายผู้รับ))

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'

ผู้ส่งแบบหลายผู้รับ:

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)

การใช้งานตัวอย่าง: # สมมติว่าผู้รับผูกกับที่อยู่กลุ่มมัลติคาสต์ด้านล่างและบางโปรแกรมขอเข้าร่วมกลุ่มนั้น และเพื่อให้กรณีง่ายขึ้นสมมติว่าผู้รับและผู้ส่งอยู่ภายใต้ซับเน็ตเดียวกัน

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

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


INADDR_ANY ไม่ได้ 'เลือกหนึ่งในอินเทอร์เฟซท้องถิ่น]'
user207421

0

ในการทำให้รหัสไคลเอนต์ (จาก tolomea) ทำงานบน Solaris คุณต้องส่งค่า ttl สำหรับIP_MULTICAST_TTLตัวเลือกซ็อกเก็ตเป็นถ่านที่ไม่ได้ลงชื่อ มิฉะนั้นคุณจะได้รับข้อผิดพลาด สิ่งนี้ใช้ได้ผลกับฉันใน Solaris 10 และ 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))

0

ตัวอย่างนี้ไม่ได้ผลสำหรับฉันด้วยเหตุผลที่คลุมเครือ

ไม่คลุมเครือมันเป็นเส้นทางง่ายๆ

บน OpenBSD

route add -inet 224.0.0.0/4 224.0.0.1

คุณสามารถกำหนดเส้นทางไปยัง dev บน Linux

route add -net 224.0.0.0 netmask 240.0.0.0 dev wlp2s0

บังคับให้การรับส่งข้อมูลแบบหลายผู้รับทั้งหมดไปยังอินเทอร์เฟซเดียวบน Linux

   ifconfig wlp2s0 allmulti

tcpdump นั้นง่ายสุด ๆ

tcpdump -n multicast

ในรหัสของคุณคุณมี:

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

ทำไมต้อง10240 ?

ขนาดแพ็กเก็ตแบบหลายผู้รับควรเป็น1316 ไบต์


-1

คำตอบของ tolomea ใช้ได้ผลสำหรับฉัน ฉันแฮ็คมันลงในsocketserver.UDPServerด้วย:

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.