Python - Windows上的多播绑定错误

3 投票
1 回答
6852 浏览
提问于 2025-04-17 18:34

我需要在Python应用程序中使用多播,经过一番搜索,我找到了一些可以工作的代码片段,下面就是:

# UDP multicast examples, Hugo Vincent, 2005-05-14.
import socket
import sys
import struct

def send(data, port=50000, addr='239.192.1.100'):
    """send(data[, port[, addr]]) - multicasts a UDP datagram."""
    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # Make the socket multicast-aware, and set TTL.
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) # Change TTL (=20) to suit
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
    # Send the data
    s.sendto(data, (addr, port))

def recv(port=50000, addr="239.192.1.100", buf_size=1024):
    """recv([port[, addr[,buf_size]]]) - waits for a datagram and returns the data."""

    # Create the socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Set some options to make it multicast-friendly
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except AttributeError:
            pass # Some systems don't support SO_REUSEPORT
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20)

    # Bind to the port
    s.bind(('', port))

    # Set some more multicast options
    intf = socket.gethostbyname(socket.gethostname())
    s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(intf))
    mreq = struct.pack("4sl", socket.inet_aton(addr), socket.INADDR_ANY)

    s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    # Receive the data, then unregister multicast receive membership, then close the port
    data, sender_addr = s.recvfrom(buf_size)
    s.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(addr) + socket.inet_aton('0.0.0.0'))
    s.close()
    return data

if __name__=="__main__":
    if sys.argv[1] == "recv":
            print recv()
    else:
            send("a")

我在绑定和多播方面遇到了一些问题。

根据我的理解,如果我在一个套接字上绑定接收消息,这样做会过滤流量。('',port)的意思是我想接收所有通过这个套接字和端口发送来的流量,不管数据包的目标IP是什么(和0.0.0.0是一样的),我们把这种情况称为情况1。

如果我使用bind((addr,port)),这也能工作。我会接收到所有目标IP是这个多播组的数据包(当然我还需要加入这个多播组),这就是情况2。

正如我所说,这两种情况都能工作,但仅限于Linux系统。

我在Windows机器上试了我的小程序,第一种情况可以正常工作,但当我尝试第二种情况时,我遇到了

    Traceback (most recent call last):
  File "test.py", line 51, in <module>
    print recv()
  File "test.py", line 32, in recv
    s.bind((addr, port))
  File "C:\Python27\lib\socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 10049] The requested address is not valid in its context

我对Windows系统不是很精通(我主要在Linux上进行开发),但我很想知道为什么我的代码在Windows系统上会出现这个错误(顺便说一下,我用的是Windows 7)。

1 个回答

5

正如Carl Cerecke在PYMOTW多播文章的评论中提到,在Windows系统中使用socket.INADDR_ANY会绑定到默认的多播地址。如果你的电脑上有多个网络接口,Windows可能会选择错误的那个。

为了避免这个问题,你可以明确指定你想要接收多播消息的网络接口:

group = socket.inet_aton(multicast_group)
iface = socket.inet_aton('192.168.1.10') # listen for multicast packets on this interface.
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, group+iface)

你可以使用以下方法获取接口列表:

socket.gethostbyname_ex(socket.gethostname()) 
# ("PCName", [], ["169.254.80.80", "192.168.1.10"])

在上面的例子中,我们可能想跳过第一个169.254的链接本地地址,选择我们想要的192.168.1.10地址。

socket.gethostbyname_ex(socket.gethostname())[2][1]
# "192.168.1.10"

撰写回答