如何使用 asyncio 的数据报端点进行广播?

问题描述 投票:0回答:1

我尝试在 asyncio 的 edp echo 客户端示例 上构建一个广播公司(用于 LAN 唤醒,但删除了下面的一些细节以保持代码简短)。但是下面的代码总是发送失败。我试过其他的非广播IP,这个没有关系。将 allow_broadcast 设置为 False 确实让代码完成。广播时如何让它工作?我在 Windows 11、Python 3.10 上。注意:我已经注释掉两行以确保套接字没有过早关闭(然后我得到其他错误,这些是以后的担心)。

import asyncio
from typing import Optional

BROADCAST_IP = "255.255.255.255"
DEFAULT_PORT = 9


class _WOLProtocol:
    def __init__(self, *messages):
        self.packets = messages
        self.done = asyncio.get_running_loop().create_future()
        self.transport = None

    def connection_made(self, transport):
        for p in self.packets:
            transport.sendto(p.encode())

        #transport.close()

    def error_received(self, exc):
        self.done.set_exception(exc)

    def connection_lost(self, exc):
        print('closing')
        #self.done.set_result(None)


async def send_magic_packet(
    *macs: str,
    ip_address: str = BROADCAST_IP,
    port: int = DEFAULT_PORT,
    interface: Optional[str] = None
) -> None:
    loop = asyncio.get_running_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: _WOLProtocol(*macs),
        remote_addr=(ip_address, port),
        allow_broadcast = True,
        local_addr=(interface, 0) if interface else None
        )

    try:
        await protocol.done
    finally:
        transport.close()

if __name__ == "__main__":
    asyncio.run(send_magic_packet('test'))

我得到的错误:

Exception in callback _ProactorDatagramTransport._loop_reading()
handle: <Handle _ProactorDatagramTransport._loop_reading()>
Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\asyncio\proactor_events.py", line 570, in _loop_reading
    self._read_fut = self._loop._proactor.recv(self._sock,
  File "C:\Program Files\Python310\lib\asyncio\windows_events.py", line 458, in recv
    ov.WSARecv(conn.fileno(), nbytes, flags)
OSError: [WinError 10022] An invalid argument was supplied

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Program Files\Python310\lib\asyncio\proactor_events.py", line 576, in _loop_reading
    self._protocol.error_received(exc)
  File ".....\code.py", line 23, in error_received
    self.done.set_exception(exc)
asyncio.exceptions.InvalidStateError: invalid state
closing
Traceback (most recent call last):
  File ".....\code.py", line 50, in <module>
    asyncio.run(send_magic_packet('test'))
  File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete
    return future.result()
  File ".....\code.py", line 45, in send_magic_packet
    await protocol.done
  File "C:\Program Files\Python310\lib\asyncio\proactor_events.py", line 530, in _loop_writing
    self._write_fut = self._loop._proactor.send(self._sock,
  File "C:\Program Files\Python310\lib\asyncio\windows_events.py", line 541, in send
    ov.WSASend(conn.fileno(), buf, flags)
OSError: [WinError 10057] A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied

作为参考,

send_magic_packet()
对应的同步函数体如下所示,效果很好:

# credit: https://github.com/remcohaszing/pywakeonlan/blob/main/wakeonlan/__init__.py
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
    if interface is not None:
        sock.bind((interface, 0))
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    sock.connect((ip_address, port))
    for packet in macs:
        sock.send(packet.encode())
python udp python-asyncio broadcast
1个回答
0
投票

好的,找到了。这是一连串的问题。

  1. 如果设置
    allow_broadcast=True
    ,则套接字永远不会连接到远程端点。因此错误。所以
    transport.sendto(p.encode())
    应该是
    transport.sendto(p.encode(), remote_addr)
  2. 但是,如果您在调用
    remote_addr
    时提供
    create_datagram_endpoint
    ,那将不起作用,
    remote_addr
    中的
    sendto()
    将被忽略。
  3. 最后,您需要确保本地地址解析为 IPv4 地址,否则 Windows API 将抛出无效指针错误。未提供时,本地地址可能会很好地解析为 IPv6 地址。

所以,发送成功的代码如下。它仍然存在

await protocol.done
永远不会返回的问题,因为
connection_lost
从未被调用过,但这将是一个新问题。

import asyncio
import socket
from typing import Optional

BROADCAST_IP = "255.255.255.255"
DEFAULT_PORT = 9


class _WOLProtocol:
    def __init__(self, remote_addr, *messages):
        self.remote_addr = remote_addr
        self.packets = messages
        self.done = asyncio.get_running_loop().create_future()
        self.transport = None

    def connection_made(self, transport):
        for p in self.packets:
            transport.sendto(p.encode(), (BROADCAST_IP,DEFAULT_PORT))

        transport.close()

    def error_received(self, exc):
        if not self.done.done():
            self.done.set_exception(exc)

    def connection_lost(self, exc):
        print('closing')
        self.done.set_result(None)


async def send_magic_packet(
    *macs: str,
    ip_address: str = BROADCAST_IP,
    port: int = DEFAULT_PORT,
    interface: Optional[str] = None
) -> None:
    loop = asyncio.get_running_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: _WOLProtocol((ip_address, port), *macs),
        family=socket.AF_INET,
        proto=socket.IPPROTO_UDP,
        allow_broadcast = True,
        local_addr=(interface, 0) if interface else None
        )

    try:
        await protocol.done
    finally:
        transport.close()

if __name__ == "__main__":
    asyncio.run(send_magic_packet('test'))
© www.soinside.com 2019 - 2024. All rights reserved.