如何在 Python 中进行 UDP 多播?
- 2025-02-20 09:24:00
- admin 原创
- 62
问题描述:
如何在 Python 中发送和接收 UDP 多播?是否有标准库可以执行此操作?
解决方案 1:
这对我有用:
收到
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
解决方案 2:
向多播组广播的多播发送方:
#!/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 'Exception'
else:
hexdata = binascii.hexlify(data)
print 'Data = %s' % hexdata
if __name__ == '__main__':
main()
解决方案 3:
更好的使用:
sock.bind((MCAST_GRP, MCAST_PORT))
而不是:
sock.bind(('', MCAST_PORT))
因为,如果您想在同一个端口上监听多个多播组,您将获得所有监听器上的所有消息。
解决方案 4:
为了加入多播组,Python 使用本机 OS 套接字接口。由于 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 帧非常简单。如果您的系统中只有单个 NIC,则发送此类数据包与发送普通 UDP 帧没有什么不同。您需要注意的只是在sendto()
方法中设置正确的目标 IP 地址。
我注意到互联网上有很多例子实际上是偶然起作用的。甚至在官方 Python 文档中也是如此。所有这些例子的问题都是错误地使用了 struct.pack。请注意,典型示例使用4sl
as 格式,并且与实际操作系统套接字接口结构不一致。
我将尝试描述在对 python 套接字对象执行 setsockopt 调用时内部发生的情况。
Python 将 setsockopt 方法调用转发到本机 C 套接字接口。Linux 套接字文档(参见man 7 ip
)介绍了ip_mreqn
IP_ADD_MEMBERSHIP 选项的两种结构形式。最短的形式为 8 个字节长,最长的形式为 12 个字节长。上面的示例生成 8 字节setsockopt
调用,其中前四个字节定义multicast_group
,后四个字节定义interface_ip
。
解决方案 5:
看一下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 的问题是由于接口不支持多播造成的?
解决方案 6:
这只是另一个答案,用于解释其他答案代码中的一些细微之处:
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], \n # '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'
解决方案 7:
要使客户端代码(来自 tolomea)在 Solaris 上运行,您需要将IP_MULTICAST_TTL
套接字选项的 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))
解决方案 8:
对于 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)
解决方案 9:
tolomea 的答案对我有用。我也将其破解为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)
扫码咨询,免费领取项目管理大礼包!