python中的asyncio是否支持用于UDP网络的基于协同路由的API?
今晚我浏览了pythonpython中的asyncio是否支持用于UDP网络的基于协同路由的API?,python,python-asyncio,Python,Python Asyncio,今晚我浏览了pythonasyncio模块文档,为我的一个课程项目寻找一些想法,但很快我发现python的标准aysncio模块中可能缺少功能 如果查看文档,您会发现有一个基于回调的API和一个基于协同路由的API。回调API可以用于构建UDP和TCP应用程序,而协同路由API似乎只能用于构建TCP应用程序,因为它使用了流式API 这给我带来了一个问题,因为我正在寻找一个用于UDP网络的基于协同路由的API,尽管我发现asyncio支持诸如和之类的低级基于协同路由的套接字方法,但是UDP网络的关
asyncio
模块文档,为我的一个课程项目寻找一些想法,但很快我发现python的标准aysncio
模块中可能缺少功能
如果查看文档,您会发现有一个基于回调的API和一个基于协同路由的API。回调API可以用于构建UDP和TCP应用程序,而协同路由API似乎只能用于构建TCP应用程序,因为它使用了流式API
这给我带来了一个问题,因为我正在寻找一个用于UDP网络的基于协同路由的API,尽管我发现asyncio
支持诸如和之类的低级基于协同路由的套接字方法,但是UDP网络的关键API,recvfrom
和sendto
并不存在
我想做的是编写一些代码,如:
async def handle_income_packet(sock):
await data, addr = sock.recvfrom(4096)
# data handling here...
await sock.sendto(addr, response)
我知道这可以使用回调API等效地实现,但这里的问题是回调不是协程,而是常规函数,因此在它中,您无法将控制权交还给事件循环并保留函数执行状态
只要看一下上面的代码,如果我们需要在数据处理部分执行一些阻塞IO操作,只要我们的IO操作也在协程中执行,那么协程版本中就不会有问题:
async def handle_income_packet(sock):
await data, addr = sock.recvfrom(4096)
async with aiohttp.ClientSession() as session:
info = await session.get(...)
response = generate_response_from_info(info)
await sock.sendto(addr, response)
只要我们使用await
,事件循环就会从该点获取控制流来处理其他事情,直到IO完成。但遗憾的是,这些代码目前不可用,因为我们没有asyncio
中的socket.sendto
和socket.recvfrom
的联合版本
我们可以在中实现的是使用传输协议回调API:
class EchoServerClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info('peername')
self.transport = transport
def data_received(self, data):
info = requests.get(...)
response = generate_response_from_info(info)
self.transport.write(response)
self.transport.close()
我们不能等待corroutine,因为回调不是corroutine,使用上面的阻塞IO调用将暂停回调中的控制流,并阻止循环处理任何其他事件,直到IO完成
另一个推荐的实现思想是在data\u received
函数中创建Future
对象,将其添加到事件循环中,并在协议类中存储任何需要的状态变量,然后显式地将控制返回到循环。虽然这可以工作,但它确实创建了许多复杂的代码,而在协同程序版本中,它们在任何方面都不需要
我们还有一个使用非阻塞套接字和add_reader
处理UDP套接字的示例。但与coroutine版本的几行代码相比,代码看起来仍然很复杂
我想说的一点是,协程是一个非常好的设计,它可以在一个线程中利用并发的能力,同时也有一个非常简单的设计模式,可以节省人力和不必要的代码行,但是,让UDP网络工作的关键部分是我们的asyncio
标准库中真正缺少的
你们怎么看
另外,如果有任何关于第三方库支持这种UDP网络API的其他建议,我将非常感谢我的课程项目。我发现它很像这样的东西,但它似乎并没有得到积极的维护
编辑:
似乎这确实实现了这个特性,但被
asyncio
开发人员拒绝了。开发人员声称,所有函数都可以使用协议传输APIcreate\u datagram\u endpoint()
实现。但正如我上面所讨论的,与在许多用例中使用回调API相比,协同路由API具有简单的功能,不幸的是,我们在UDP中没有这些功能。没有提供基于流的API的原因是,流在回调的基础上提供排序,而UDP通信本质上是无序的,因此,这两者从根本上是不相容的
但这并不意味着你不能从你的回调中调用协同路由——事实上这很容易!从开始,您可以执行以下操作:
def datagram_received(self, data, addr):
loop = asyncio.get_event_loop()
loop.create_task(self.handle_income_packet(data, addr))
async def handle_income_packet(self, data, addr):
# echo back the message, but 2 seconds later
await asyncio.sleep(2)
self.transport.sendto(data, addr)
在这里,
datagram\u received
启动您的handle\u income\u数据包
corroutine,它可以免费等待任意数量的corroutine。由于协同程序在“后台”运行,因此事件循环在任何时候都不会被阻塞,并且收到的数据报会立即返回,正如预期的那样。您可能会感兴趣:
在异步IO中提供易于使用的UDP套接字
以下是一个例子:
导入异步IO
导入异步UDP
异步def main():
sock=wait asyncudp.create_socket(远程地址=('127.0.0.1',9999))
sock.sendto(b'Hello!')
打印(等待sock.recvfrom())
sock.close()
asyncio.run(main())
是asyncore模块处理您所描述的内容,而不是asyncio@dmitryro。asyncore模块已被弃用,asyncio至少也处理UDP—请参阅示例。无论如何,asyncore都不会帮助OP,因为它的事件循环不支持协同路由。@user4815162342很高兴知道。谢谢。虽然我个人倾向于避免在设计模式中使用回调,但这个解决方案对我来说无疑是一个足够聪明的解决方案,可以最大限度地限制回调的使用。我接受这个。谢谢接下来的一个问题是,如果我倾向于使用带有asyncio
的低级套接字接口,比如说我想发送ICMP数据包,那么使用add_reader
是唯一的解决方案吗?@Chaserhkj在这种情况下,您可以将回调视为一个设计选择,而不仅仅是一个API入口点,仅仅是对协同程序世界的践踏。我从未使用过ICMP数据包,所以我不敢猜测,但这可能是另一个问题的素材。
async def main():
# Create a local UDP enpoint
local = await open_local_endpoint('localhost', 8888)
# Create a remote UDP enpoint, pointing to the first one
remote = await open_remote_endpoint(*local.address)
# The remote endpoint sends a datagram
remote.send(b'Hey Hey, My My')
# The local endpoint receives the datagram, along with the address
data, address = await local.receive()
# Print: Got 'Hey Hey, My My' from 127.0.0.1 port 50603
print(f"Got {data!r} from {address[0]} port {address[1]}")