如何在 Python 中进行 UDP 多播?

How do you UDP multicast in Python?

提问人: 提问时间:3/3/2009 最后编辑:3 revs, 2 users 67%NoName 更新时间:3/2/2023 访问量:171397

问:

如何在 Python 中发送和接收 UDP 多播?有没有标准库可以这样做?

Python 组播

评论


答:

22赞 7 revs, 7 users 83%Niranjan Tulpule #1

广播到组播组的组播发送方:

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

评论

1赞 Gordon Wrigley 11/25/2009
我试过了,它没有用。在 Wireshark 中,我可以看到传输,但我没有看到任何 IGMP 加入的东西,也没有收到任何东西。
2赞 stefanB 1/22/2013
您需要绑定到组播组/端口,而不是组播地址上的本地端口,sock.bind((MCAST_GRP, MCAST_PORT))
1赞 Brian Bulkowski 7/13/2016
这个例子对我不起作用,原因不明。使用 socket.gethostbyname(socket.gethostname()) 来选择接口并不总是选择外部接口——事实上,在 Debian 系统上,它倾向于选择环回地址。Debian 在主机表中为主机名添加了一个条目 127.0.1.1。相反,使用套接字更有效。INADDR_ANY ,排名较高的答案通过 'pack' 语句使用(它比 '+' 更正确)。此外,不需要使用IP_MULTICAST_IF,因为排名较高的答案正确指出。
1赞 Mike S 9/3/2016
@BrianBulkowski有很多程序员使用套接字。INADDR_ANY,令我们这些拥有多个接口的人感到非常痛苦和惊愕,他们需要在特定接口上传输组播数据。解决方案不是套接字。INADDR_ANY。它是通过IP地址选择正确的接口,但您认为最好(一个配置文件,询问最终用户,无论您如何选择应用程序的需要)。插座。INADDR_ANY 将为您提供组播数据,true,如果您假设是单宿主主机,这是最简单的,但我认为它不太正确。
0赞 Brian Bulkowski 11/19/2016
@MikeS虽然我在某些原则上同意你的观点,但使用 IP 地址选择接口的想法非常非常令人担忧。我很清楚这个问题,但在一个动态的世界中,IP 地址不是答案。因此,您需要编写代码来迭代所有内容并按接口名称进行选择,查看接口名称,选出当前 IP 地址,然后使用它。希望在此期间 IP 地址没有更改。我希望 Linux/Unix 在任何地方都使用接口名称标准化,并且编程语言也标准化了,这将使配置文件更加合理。
123赞 6 revs, 4 users 78%Gordon Wrigley #2

这对我有用:

收到

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 中文文档

评论

7赞 atikat 9/8/2011
对于 mac os x,您需要使用套接字。SO_REUSEPORT选项作为套接字的替代品。SO_REUSEADDR在上面的示例中,允许同一组播端口地址组合上的多个侦听器。
1赞 Mark Foreman 11/9/2012
对于发送,我还需要“sock.bind((<local ip>, 0))”,因为我的组播侦听器绑定到特定的适配器。
2赞 stefanB 1/22/2013
对于 UDP 组播,您需要绑定到组播组/端口,而不是本地组端口,您的代码可能有效,也可能无效,当您有多个 NIC 时,它可能不起作用sock.bind((MCAST_GRP, MCAST_PORT))
0赞 Kyuubi 11/4/2013
@atikat : 谢谢!!虽然为什么我们在MAC上需要这个,但在Ubuntu上不需要?
4赞 stewbasic 3/22/2016
@RandallCook:当我用MCAST_GRP替换“”时,我得到socket.error:[Errno 10049]请求的地址在其上下文中无效
2赞 2 revswroniasty #3

看看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的问题是由不支持组播的接口引起的?

14赞 2 revs, 2 users 91%st0ne #4

更好地使用:

sock.bind((MCAST_GRP, MCAST_PORT))

而不是:

sock.bind(('', MCAST_PORT))

因为,如果要在同一端口上侦听多个组播组,则将获得所有侦听器上的所有消息。

-1赞 tompreston #5

托洛梅亚的回答对我有用。我把它黑进了套接字服务器。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)
0赞 bunnyrabbit #6

要使客户机代码(来自 tolomea)在 Solaris 上工作,您需要将套接字选项的 ttl 值作为无符号字符传递。否则,您将收到错误。 这在 Solaris 10 和 11 上对我有用:IP_MULTICAST_TTL

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))
10赞 2 revs, 2 users 97%Leszek Wojcik #7

为了加入组播组,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 帧非常简单。如果系统中有单个 NIC,则发送此类数据包与发送通常的 UDP 帧没有区别。您需要注意的只是在方法中设置正确的目标 IP 地址。sendto()

我注意到很多关于互联网的例子实际上是偶然的。即使在官方的 python 文档上。所有这些问题都错误地使用了struct.pack。请注意,典型示例用作格式,与实际的操作系统套接字接口结构不一致。4sl

我将尝试描述在对 python 套接字对象执行 setsockopt 调用时在引擎盖下发生的情况。

Python 将 setsockopt 方法调用转发到原生 C 套接字接口。Linux 套接字文档(参见 )介绍了IP_ADD_MEMBERSHIP选项的两种结构形式。最短的是 form 的长度为 8 个字节,最长的为 12 个字节。上面的示例生成 8 字节调用,其中前四个字节定义,后四个字节定义。man 7 ipip_mreqnsetsockoptmulticast_groupinterface_ip

3赞 2 revspterodragon #8

只是另一个答案来解释其他答案代码中的一些微妙之处:

  • 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'

评论

0赞 user207421 7/29/2019
INADDR_ANY不会“选择本地接口之一”。
0赞 2 revsLeroy Scandal #9

GumD 和 GumC https://github.com/futzu/gumd

我主要将 GumD 用于视频,但您可以使用任何类型的文件。

    pip3 install  gumd

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

使用 gumc(客户端)


    >>>> from gumc import GumC
    >>>> gumc = GumC("udp://@235.35.3.5:3535")
    >>>> data = gumc.read(8)
    >>>> data
    b'Helloooo'
0赞 Mountain #10

对于那里的 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)

评论

1赞 Leroy Scandal 3/9/2023
我今天正在讨论这件事,我很高兴我看到了你的帖子,我正在推动插座。IPPROTO_IP,套接字。IP_MULTICAST_IF,我没有很好的论据反对使用SOL_SOCKET,但你只是给了我一个,谢谢伙计。