我正在编写一个需要多播发现服务的系统,类似于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()
这似乎正确地发送消息,并且服务器打印出它已收到消息并发回响应。但客户从未收到回复。
我需要设置一些额外的参数吗?