您如何在Python中进行UDP多播?


86

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

Answers:


98

这对我有用:

接收

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


6
对于Mac OS X,您需要在上面的示例中使用socket.SO_REUSEPORT选项替代socket.SO_REUSEADDR,以允许在同一多播端口地址组合上使用多个侦听器。
atikat 2011年

对于发送,我还需要“ sock.bind((<本地IP>,0))”,因为我的多播侦听器已绑定到特定的适配器。
Mark Foreman

2
对于udp组播,您需要绑定到组播组/端口而不是本地组端口,sock.bind((MCAST_GRP, MCAST_PORT))您的代码可能并且可能不起作用,当您有多个
nic

@atikat:谢谢!虽然为什么我们在MAC上需要此功能,但在Ubuntu上却不需要?
Kyuubi

2
@RandallCook:当我用MCAST_GRP替换''时,我得到了socket.error:[Errno 10049]所请求的地址在其上下文中无效
stewbasic

17

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

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

我试过了,没有用。在Wireshark中,我可以看到传输,但是看不到任何IGMP加入的东西,也什么也没收到。
戈登·箭牌

1
您需要绑定到多播组/端口而不是多播地址上的本地端口sock.bind((MCAST_GRP, MCAST_PORT))
stefanB 2013年

1
由于一个晦涩的原因,此示例对我不起作用。使用socket.gethostbyname(socket.gethostname())选择接口并不总是选择外部接口-实际上,在debian系统上,它倾向于选择回送地址。Debian在主机表中为主机名添加了127.0.1.1条目。相反,使用socket.INADDR_ANY会更有效,后者会通过'pack'语句(比'+'更正确)使用较高排名的答案。同样,由于较高答案的正确说明,也不需要使用IP_MULTICAST_IF。
Brian Bulkowski

1
@BrianBulkowski有很多使用socket.INADDR_ANY的程序员,这给我们这些具有多个接口的人们带来了极大的麻烦和震惊,他们需要多播数据才能进入特定的接口。解决方案不是socket.INADDR_ANY。可以通过IP地址选择适当的接口,但是您认为最好(配置文件,询问最终用户,但是可以根据应用程序的需要进行选择)。socket.INADDR_ANY会为您提供多播数据,这是正确的,并且如果您假设是单宿主主机,这将是最简单的操作,但是我认为它的正确性较差。
Mike S

@MikeS虽然我在某些原则上同意您的观点,但是使用IP地址选择接口的想法非常烦人。我很清楚这个问题,但是在动态世界中,IP地址不是解决方案。因此,您需要编写迭代所有内容的代码,并按接口名称进行选择,查看接口名称,挑选出当前IP地址,然后使用该地址。希望在此期间,IP地址没有更改。我希望Linux / Unix在所有地方都使用接口名称方面已经标准化,而编程语言也已经这样做,这将使配置文件更加合理。
Brian Bulkowski

13

更好地使用:

sock.bind((MCAST_GRP, MCAST_PORT))

代替:

sock.bind(('', MCAST_PORT))

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


6

为了加入多播组,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地址即可。

我注意到实际上有关Internet的许多示例都是偶然的。即使在官方的python文档上。他们所有的问题都没有正确使用struct.pack。请注意,典型示例4sl用作格式,并且与实际的OS套接字接口结构不符。

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

Python将setsockopt方法调用转发到本地C套接字接口。Linux套接字文档(请参阅参考资料man 7 ip)介绍ip_mreqn了IP_ADD_MEMBERSHIP选项的两种结构形式。最短的形式是8个字节长,更长的是12个字节长。上面的示例生成8字节setsockopt调用,其中前四个字节定义multicast_group,后四个字节定义interface_ip


2

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


2

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

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


INADDR_ANY并没有“选择本地接口之一]”。
罗恩侯爵,

0

要使客户机代码(来自tolomea)在Solaris上工作,您需要将IP_MULTICAST_TTLsocket选项的ttl值作为未签名的char传递。否则,您将得到一个错误。这在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))

-1

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.