Як здійснюється багатоадресне передавання UDP в Python?


86

Як надсилати та отримувати багатоадресну передачу UDP в Python? Чи існує така стандартна бібліотека?

Відповіді:


98

Це працює для мене:

Отримати

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 Tue Nov 10 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
Для mac os x вам потрібно використовувати опцію socket.SO_REUSEPORT як альтернативу socket.SO_REUSEADDR у наведеному вище прикладі, щоб дозволити кільком слухачам на одній комбінації адрес багатоадресного порту.
atikat

Для надсилання мені також знадобився "sock.bind ((<local ip>, 0))", оскільки мій багатоадресний прослуховувач був прив'язаний до певного адаптера.
Mark Foreman

2
для udp multicast вам потрібно прив’язати до групи / порту багатоадресної передачі, а не до порту локальної групи sock.bind((MCAST_GRP, MCAST_PORT)),, ваш код може працювати, а може і не працювати, він може не працювати, якщо у вас є кілька ніків
stefanB

@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, і я нічого не отримую.
Гордон Ріглі

1
вам потрібно прив’язати до групи багатоадресної sock.bind((MCAST_GRP, MCAST_PORT))
розсилки

1
Цей приклад не працює для мене з незрозумілої причини. Використання socket.gethostbyname (socket.gethostname ()) для вибору інтерфейсу не завжди обирає зовнішній інтерфейс - насправді в системах debian він прагне вибрати адресу зворотного зв'язку. Debian додає запис 127.0.1.1 до таблиці хостів для імені хосту. Натомість ефективніше використовувати socket.INADDR_ANY, який вища відповідь використовує за допомогою оператора 'pack' (що є більш правильним, ніж '+'). Крім того, використання IP_MULTICAST_IF не потрібно, як правильно відповідає відповідь вищого рангу.
Брайан Булковскі

1
@BrianBulkowski є багато програмістів, які використовують socket.INADDR_ANY, на велике горе та здивування тих з нас, що мають кілька інтерфейсів, яким потрібні багатоадресні дані, щоб надходити на певний інтерфейс. Рішення не є socket.INADDR_ANY. Це вибір правильного інтерфейсу за IP-адресою, однак ви вважаєте, що це найкраще (файл конфігурації, який запитує кінцевого користувача, однак ви обираєте для потреб своєї програми). socket.INADDR_ANY отримає вам дані багатоадресної передачі, правда, і це найпростіше, якщо ви припускаєте одноходовий хост, але я думаю, що це менш правильно.
Mike S

@MikeS, хоча я погоджуюся з тобою в якомусь принципі, ідея використання IP-адрес для вибору інтерфейсів страшенно, страшенно чревата. Я добре знаю проблему, але в динамічному світі IP-адреса не є відповіддю. Тому вам потрібно написати код, який все повторює та вибирає за назвою інтерфейсу, переглядає ім’я інтерфейсу, виділяє поточну IP-адресу та використовує це. Сподіваємось, IP-адреса тим часом не змінилася. Я хотів би, щоб Linux / Unix стандартизував використання назв інтерфейсів скрізь, а мови програмування мали, що зробить конфігураційний файл більш розумним.
Брайан Булковскі

13

Краще використання:

sock.bind((MCAST_GRP, MCAST_PORT))

замість:

sock.bind(('', MCAST_PORT))

тому що, якщо ви хочете слухати кілька груп багатоадресної передачі на одному порту, ви отримаєте всі повідомлення на всіх слухачах.


6

Для приєднання до групи багатоадресної передачі Python використовує власний інтерфейс сокета ОС. Завдяки переносимості та стабільності середовища Python багато опцій сокета безпосередньо переадресовуються на виклик набору власних сокетів. Багатоадресний режим роботи, такий як приєднання та відмова від членства в групі, може бути здійснений 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-кадрів відбувається прямо вперед. Якщо у вашій системі є одна мережева карта, яка відправляє такі пакети, вона не відрізняється від звичайної передачі кадрів UDP. Все, про що вам потрібно подбати, це просто встановити правильну IP-адресу призначення в sendto()методі.

Я помітив, що багато прикладів навколо Інтернету працюють насправді випадково. Навіть в офіційній документації на python. Проблема для всіх них - неправильне використання struct.pack. Зверніть увагу, що типовий приклад використовується 4slяк формат, і він не узгоджується з фактичною структурою інтерфейсу сокета ОС.

Я спробую описати, що відбувається під капотом під час здійснення виклику setsockopt для об'єкта сокета python.

Python перенаправляє виклик методу setsockopt до власного інтерфейсу сокета C. Документація сокета Linux (див. man 7 ip) Представляє дві форми ip_mreqnструктури для опції IP_ADD_MEMBERSHIP. Найкоротша форма - 8 байт, а довша - 12 байт. Наведений вище приклад генерує 8-байтовий setsockoptвиклик, де визначають перші чотири байти, multicast_groupа другі - чотири байти interface_ip.


2

Погляньте на py-multicast . Мережевий модуль може перевірити, чи підтримує інтерфейс багатоадресну передачу (принаймні на 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 не 'вибирає один із локальних інтерфейсів]'.
Маркіз

0

Щоб змусити код клієнта (з tolomea) працювати на Solaris, вам потрібно передати значення ttl для IP_MULTICAST_TTLопції socket як беззнаковий символ. В іншому випадку ви отримаєте помилку. Це працювало для мене на 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))

-1

відповідь толомеї спрацювала на мене. Я зламав його на 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.