Как вы UDP многоадресной рассылки в Python?
Как вы отправляете и получаете многоадресную рассылку UDP в Python? Существует ли стандартная библиотека для этого?
13 ответов
Это работает для меня:
Получать
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:
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)
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
Многоадресный отправитель, который передает в многоадресную группу:
#!/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()
Лучшее использование:
sock.bind((MCAST_GRP, MCAST_PORT))
вместо:
sock.bind(('', MCAST_PORT))
потому что, если вы хотите прослушивать несколько групп многоадресной рассылки на одном и том же порту, вы получите все сообщения от всех слушателей.
Для присоединения к многоадресной группе Python использует собственный интерфейс сокетов ОС. Благодаря переносимости и стабильности среды Python многие параметры сокетов напрямую перенаправляются на собственный вызов сокета setsockopt. Многоадресный режим работы, такой как присоединение и удаление членов группы, может быть выполнен 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
,
Просто еще один ответ, чтобы объяснить некоторые тонкие моменты в коде других ответов:
socket.INADDR_ANY
- это на самом деле не связывает все интерфейсы, а просто выбирает один из локальных интерфейсов- Присоединение к многоадресной группе - это не то же самое, что привязка сокета к адресу локального интерфейса.
см. Что означает связывание многоадресного (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'
Посмотрите на 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, были вызваны интерфейсом, не поддерживающим многоадресную передачу?
Для этого есть платформа http://twistedmatrix.com/trac/. Вот пример https://twistedmatrix.com/documents/12.2.0/core/howto/udp.html
Чтобы клиентский код (из 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))
GumD и GumC https://github.com/futzu/gumd
Я использую GumD в основном для видео, но вы можете использовать любые файлы.
pip3 install gumd
гумд (демон)
>>>> from gumd import GumD
>>>> gumd =GumD('235.35.3.5:3535',1)
>>>> gumd.mcast("/home/a/stuff.txt")
# Can also use http(s), UDP and multicast
stream uri: udp://@235.35.3.5:3535
>>>>
Использовать гумк (Клиент)
>>>> from gumc import GumC
>>>> gumc = GumC("udp://@235.35.3.5:3535")
>>>> data = gumc.read(8)
>>>> data
b'Helloooo'
Для программистов Windows следующий фрагмент кода работает как в Windows, так и в Linux.
Примеры, в которых используется SOL_IP, вызовут ошибки в Windows для версий Python >3.x.12.
Многие примеры не включают настройку IP_MULTICAST_IF. Это может быть важно для систем с несколькими интерфейсами. В Windows IP_MULTICAST_IF требуется для указания сетевого интерфейса, поскольку Windows не может выполнить привязку к многоадресному адресу.
import socket
import platform
from contextlib import closing
address = "239.50.50.50"
port = 6000
network_adapter = "172.16.0.93"
with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)) as sock:
# SO_REUSEADDR: allows binding to port potentially already in use
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# linux binds to multicast address, windows to interface address
ip_bind = network_adapter if platform.system() == "Windows" else address
sock.bind((ip_bind, port))
# socket.IPPROTO_IP works on Linux and Windows
# IP_MULTICAST_IF: force sending network traffic over specific network adapter
# IP_ADD_MEMBERSHIP: join multicast group
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(network_adapter))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(address) + socket.inet_aton(network_adapter))
# send recv examples
# sock.sendto(b"Hello World", (address, port))
# data, server = sock.recvfrom(2**8)
Этот пример мне не подходит по непонятной причине.
Не малопонятно, это простая маршрутизация.
На OpenBSD
route add -inet 224.0.0.0/4 224.0.0.1
Вы можете указать путь к разработчику в 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 байт
Ответ Толомеи работал на меня. Я взломал его в 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)
Многоадресный трафик ничем не отличается от обычного UDP, за исключением IP-адреса. Взгляните на стандартную библиотеку сокетов. Возможно, вам удастся найти то, что основано на сокете и проще в использовании.