Як надсилати та отримувати багатоадресну передачу UDP в Python? Чи існує така стандартна бібліотека?
Відповіді:
Це працює для мене:
Отримати
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
sock.bind((MCAST_GRP, MCAST_PORT))
,, ваш код може працювати, а може і не працювати, він може не працювати, якщо у вас є кілька ніків
Відправник багатоадресної передачі, який транслює групу багатоадресної передачі:
#!/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))
Для приєднання до групи багатоадресної передачі 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
.
Погляньте на 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 спричинені інтерфейсом, що не підтримує багатоадресну передачу?
Просто ще одна відповідь, щоб пояснити деякі тонкі моменти в коді інших відповідей:
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'
Щоб змусити код клієнта (з 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))
відповідь толомеї спрацювала на мене. Я зламав його на 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)