Python asyncio:如何接收组播响应?

时间:2017-01-01 18:43:27

标签: python sockets udp multicast python-asyncio

我正在编写一个需要多播发现服务的系统,类似于UPnP的工作方式。我正在尝试使用Python 3.6的asyncio库来编写它。我有一个工作的asyncio服务器,它可以接收多播数据包并正确响应。我有一个工作的非asyncio客户端,它发送一个发现多播数据包并接收回复。但是,我无法弄清楚如何使用asyncio代码重写客户端。

感谢this Reddit post我有一个可以监听特定广播地址/端口的工作服务器:

class DiscoveryServerProtocol:

    def __init__(self, servers):
        self.servers = servers


    def connection_made(self, transport):
        self.transport = transport


    def datagram_received(self, data, addr):
        print('Received {!r} from {!r}'.format(data, addr))
        if data != DISCOVERY_MESSAGE:
            print("{!r} != {!r}".format(data, DISCOVERY_MESSAGE))
            return

        data = json.dumps({
            "name": socket.gethostname(),
            "type": DISCOVERY_TYPES.MEDIA_SERVER,
            "servers": self.servers,
        }).encode("ascii")
        print('Send {!r} to {!r}'.format(data, addr))
        self.transport.sendto(data, addr)


def start_discovery_server(loop, servers):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('', DISCOVERY_BROADCAST_PORT))
    group = socket.inet_aton(DISCOVERY_BROADCAST_ADDR)
    mreq = struct.pack('4sL', group, socket.INADDR_ANY)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

    listen = loop.create_datagram_endpoint(
        lambda: DiscoveryServerProtocol(servers),
        sock=sock,
    )
    transport, protocol = loop.run_until_complete(listen)

但是,我找不到编写客户端的asyncio版本的方法。以下非asyncio 代码可以正常工作:

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(3)
ttl = struct.pack('b', 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

received = []

try:
    sent = sock.sendto(
        DISCOVERY_MESSAGE,
        (DISCOVERY_BROADCAST_ADDR, DISCOVERY_BROADCAST_PORT)
    )

    while True:
        try:
            data, server = sock.recvfrom(1024)
        except socket.timeout:
            break
        else:
            received.append(json.loads(data))

finally:
    sock.close()

print(received)

当我运行它时,服务器在另一个窗口中运行,客户端打印出它已发送多播数据包,然后服务器几乎立即打印出已收到它并发送响应,然后在三个之后第二次超时,客户端打印出包含服务器响应的列表。这很像我想要的,但它不是asyncio。

将其移植到我管理的asyncio的最佳尝试是:

class DiscoveryClientProtocol:
    def connection_made(self, transport):
        self.transport = transport
        sock = self.transport.get_extra_info('socket')
        ttl = struct.pack('b', 1)
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

        print('Send:', DISCOVERY_MESSAGE)
        self.transport.sendto(DISCOVERY_MESSAGE)

    def datagram_received(self, data, addr):
        print("Received:", data.decode())

        print("Close the socket")
        self.transport.close()

    def error_received(self, exc):
        print('Error received:', exc)

    def connection_lost(self, exc):
        print("Socket closed, stop the event loop")
        loop = asyncio.get_event_loop()
        loop.stop()

loop = asyncio.get_event_loop()
message = DISCOVERY_MESSAGE
connect = loop.create_datagram_endpoint(
    DiscoveryClientProtocol,
    remote_addr=(DISCOVERY_BROADCAST_ADDR, DISCOVERY_BROADCAST_PORT),

)
transport, protocol = loop.run_until_complete(connect)
loop.run_forever()
transport.close()
loop.close()

这似乎正确地发送消息,并且服务器打印出它已收到消息并发回响应。但客户从未收到回复。

我需要设置一些额外的参数吗?

0 个答案:

没有答案