Làm cách nào để bạn UDP multicast trong Python?


86

Làm cách nào để bạn gửi và nhận UDP multicast trong Python? Có một thư viện tiêu chuẩn để làm như vậy không?

Câu trả lời:


98

Điều này phù hợp với tôi:

Nhận được

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ửi

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

Nó dựa trên các ví dụ từ http://wiki.python.org/moin/UdpCommunication không hoạt động.

Hệ thống của tôi là ... Linux 2.6.31-15-generic # 50-Ubuntu SMP Thứ ba ngày 10 tháng 11 14:54:29 UTC 2009 i686 GNU / Linux Python 2.6.4


6
Đối với mac os x, bạn cần sử dụng tùy chọn socket.SO_REUSEPORT thay thế cho socket.SO_REUSEADDR trong ví dụ trên, để cho phép nhiều người nghe trên cùng một tổ hợp địa chỉ cổng phát đa hướng.
atikat

Để gửi, tôi cũng cần "sock.bind ((<local ip>, 0))" vì trình nghe đa hướng của tôi bị ràng buộc với một bộ điều hợp cụ thể.
Mark Foreman vào

2
cho udp multicast bạn cần phải bám vào multicast nhóm / cổng không phải là cổng nhóm địa phương, sock.bind((MCAST_GRP, MCAST_PORT)), sức mạnh mã của bạn và có thể không làm việc, nó có thể không làm việc khi bạn có nhiều NIC
stefanB

@atikat: Cảm ơn !! Mặc dù tại sao chúng ta cần điều này trên MAC mà không phải trên Ubuntu?
Kyuubi

2
@RandallCook: Khi tôi thay thế '' bằng MCAST_GRP, tôi nhận được socket.error: [Errno 10049] Địa chỉ được yêu cầu không hợp lệ trong ngữ cảnh của nó
piton

17

Người gửi đa hướng phát đến một nhóm đa hướng:

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

Bộ thu đa hướng đọc từ một nhóm đa hướng và in dữ liệu hex vào bảng điều khiển:

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

Tôi đã thử điều này, nó không hoạt động. Trong Wireshark, tôi có thể thấy truyền, nhưng tôi không thấy bất kỳ nội dung tham gia IGMP nào và tôi không nhận được bất kỳ thứ gì.
Gordon Wrigley

1
bạn cần phải bám vào multicast nhóm / cổng không cảng địa phương trên địa chỉ multicast,sock.bind((MCAST_GRP, MCAST_PORT))
stefanB

1
Ví dụ này không phù hợp với tôi, vì một lý do khó hiểu. Sử dụng socket.gethostbyname (socket.gethostname ()) để chọn giao diện không phải lúc nào cũng chọn giao diện bên ngoài - trên thực tế, trên các hệ thống debian, nó có xu hướng chọn địa chỉ vòng lặp. Debian thêm một mục nhập 127.0.1.1 trong bảng máy chủ cho tên máy chủ. Thay vào đó, sẽ hiệu quả hơn nếu sử dụng socket.INADDR_ANY, mà câu trả lời xếp hạng cao hơn sử dụng thông qua câu lệnh 'pack' (đúng hơn câu lệnh '+'). Ngoài ra, việc sử dụng IP_MULTICAST_IF là không bắt buộc, vì câu trả lời xếp hạng cao hơn đã nêu chính xác.
Brian Bulkowski

1
@BrianBulkowski có rất nhiều lập trình viên sử dụng socket.INADDR_ANY, thật khốn khổ và kinh ngạc cho những người trong chúng ta với nhiều giao diện, cần dữ liệu đa hướng để có trên một giao diện cụ thể. Giải pháp không phải là socket.INADDR_ANY. Đó là chọn giao diện phù hợp theo địa chỉ IP, theo cách bạn nghĩ là tốt nhất (tệp cấu hình, yêu cầu người dùng cuối, tuy nhiên bạn chọn cho nhu cầu của ứng dụng của mình). socket.INADDR_ANY sẽ cung cấp cho bạn dữ liệu đa hướng, true và dễ dàng nhất nếu bạn giả sử là một máy chủ lưu trữ đơn, nhưng tôi nghĩ điều đó ít chính xác hơn.
Mike S

@MikeS trong khi tôi đồng ý với bạn về một số nguyên tắc, thì ý tưởng sử dụng địa chỉ IP để chọn giao diện là một điều tồi tệ, vô cùng tồi tệ. Tôi biết rõ vấn đề, nhưng trong một thế giới năng động và địa chỉ IP không phải là câu trả lời. Vì vậy, bạn cần phải viết mã lặp lại mọi thứ và chọn theo tên giao diện, xem tên giao diện, chọn địa chỉ IP hiện tại và sử dụng địa chỉ đó. Hy vọng rằng địa chỉ IP không thay đổi trong thời gian chờ đợi. Tôi ước rằng Linux / Unix đã tiêu chuẩn hóa việc sử dụng tên giao diện ở mọi nơi và ngôn ngữ lập trình có, điều này sẽ làm cho tệp cấu hình trở nên hợp lý hơn.
Brian Bulkowski,

13

Sử dụng tốt hơn:

sock.bind((MCAST_GRP, MCAST_PORT))

thay vì:

sock.bind(('', MCAST_PORT))

bởi vì, nếu bạn muốn nghe nhiều nhóm phát đa hướng trên cùng một cổng, bạn sẽ nhận được tất cả thông báo trên tất cả các trình nghe.


6

Để tham gia nhóm đa hướng, Python sử dụng giao diện ổ cắm hệ điều hành gốc. Do tính di động và tính ổn định của môi trường Python, nhiều tùy chọn socket được chuyển tiếp trực tiếp đến lệnh gọi setsockopt socket gốc. Chỉ có thể thực hiện chế độ hoạt động đa phương thức như tham gia và bỏ thành viên nhóm setsockopt.

Chương trình cơ bản để nhận gói IP multicast có thể giống như sau:

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)

Đầu tiên nó tạo socket, liên kết nó và kích hoạt kích hoạt tham gia nhóm đa phương bằng cách phát hành setsockopt. Cuối cùng, nó sẽ nhận các gói tin mãi mãi.

Việc gửi các khung IP multicast được chuyển thẳng về phía trước. Nếu bạn có một NIC trong hệ thống của mình, việc gửi các gói như vậy không khác với việc gửi các khung UDP thông thường. Tất cả những gì bạn cần quan tâm là chỉ đặt địa chỉ IP đích trong sendto()phương thức.

Tôi nhận thấy rằng rất nhiều ví dụ xung quanh Internet hoạt động một cách tình cờ. Ngay cả trên tài liệu chính thức về python. Vấn đề đối với tất cả chúng là sử dụng struct.pack không đúng cách. Xin lưu ý rằng ví dụ điển hình sử dụng 4sllàm định dạng và nó không phù hợp với cấu trúc giao diện socket hệ điều hành thực tế.

Tôi sẽ cố gắng mô tả những gì xảy ra bên dưới mui xe khi thực hiện lệnh gọi setsockopt cho đối tượng ổ cắm python.

Python chuyển tiếp cuộc gọi phương thức setsockopt tới giao diện ổ cắm C gốc. Tài liệu về ổ cắm Linux (xem man 7 ip) giới thiệu hai dạng ip_mreqncấu trúc cho tùy chọn IP_ADD_MEMBERSHIP. Ngắn nhất là biểu mẫu dài 8 byte và dài hơn là 12 byte. Ví dụ trên tạo ra setsockoptlệnh gọi 8 byte trong đó bốn byte đầu tiên xác định multicast_groupvà bốn byte thứ hai xác định interface_ip.


2

Hãy xem py-multicast . Mô-đun mạng có thể kiểm tra xem giao diện có hỗ trợ phát đa hướng hay không (ít nhất là trên 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

Có lẽ vấn đề không nhìn thấy IGMP, do giao diện không hỗ trợ phát đa hướng?


2

Chỉ là một câu trả lời khác để giải thích một số điểm tinh tế trong mã của các câu trả lời khác:

  • socket.INADDR_ANY- (Đã chỉnh sửa) Trong bối cảnh IP_ADD_MEMBERSHIP, điều này không thực sự ràng buộc socket với tất cả các giao diện mà chỉ cần chọn giao diện mặc định nơi phát đa hướng (theo bảng định tuyến)
  • Tham gia một nhóm đa hướng không giống như liên kết một ổ cắm với một địa chỉ giao diện cục bộ

xem Liên kết một ổ cắm đa hướng (UDP) có nghĩa là gì? để biết thêm về cách hoạt động của multicast

Bộ thu Multicast:

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)

cách sử dụng mẫu: (chạy phần dưới đây trong hai bảng điều khiển và chọn --giface của riêng bạn (phải giống với giao diện nhận dữ liệu multicast))

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'

Người gửi đa phương:

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)

cách sử dụng mẫu: # giả sử máy thu liên kết với địa chỉ nhóm phát đa hướng bên dưới và một số chương trình yêu cầu tham gia nhóm đó. Và để đơn giản hóa trường hợp, giả sử người nhận và người gửi nằm trong cùng một mạng con

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

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


INADDR_ANY không 'chọn một trong các giao diện cục bộ]'.
Marquis of Lorne,

0

Để mã máy khách (từ tolomea) hoạt động trên Solaris, bạn cần chuyển giá trị ttl cho IP_MULTICAST_TTLtùy chọn socket dưới dạng ký tự không dấu. Nếu không bạn sẽ gặp lỗi. Điều này đã làm việc cho tôi trên Solaris 10 và 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

câu trả lời của tolomea phù hợp với tôi. Tôi cũng đã hack nó vào 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.