我尝试在 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())
好的,找到了。这是一连串的问题。
allow_broadcast=True
,则套接字永远不会连接到远程端点。因此错误。所以transport.sendto(p.encode())
应该是transport.sendto(p.encode(), remote_addr)
remote_addr
时提供 create_datagram_endpoint
,那将不起作用,remote_addr
中的 sendto()
将被忽略。所以,发送成功的代码如下。它仍然存在
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'))