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:
Đ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
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
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()
sock.bind((MCAST_GRP, MCAST_PORT))
Để 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 4sl
là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_mreqn
cấ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 setsockopt
lệnh gọi 8 byte trong đó bốn byte đầu tiên xác định multicast_group
và bốn byte thứ hai xác định interface_ip
.
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?
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)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'
Để 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_TTL
tù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))
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)