来自python的asyncio是否支持基于协程的API用于UDP网络?

Cha*_*hkj 7 python python-asyncio

我浏览了Python asyncio模块文档这个夜晚寻找一些想法,我当然项目之一,但我很快就发现,有可能会在Python的标准缺乏功能的aysncio模块.

如果你查看文档,你会发现有一个基于回调的API和一个基于协程的API.并且回调API可以用于构建UDP和TCP应用程序,而它看起来协程API只能用于构建TCP应用程序,因为它使用了流式API.

这颇使我的问题,因为我一直在寻找的UDP网络基于协同程序的API,虽然我没有发现asyncio支持低层协程基于套接字的方法,如sock_recvsock_sendall,但对于UDP网络的关键的API,recvfrom并且sendto不存在.

我想做的是写一些代码,如:

async def handle_income_packet(sock):
    await data, addr = sock.recvfrom(4096)
    # data handling here...
    await sock.sendto(addr, response)
Run Code Online (Sandbox Code Playgroud)

我知道这可以使用回调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)
Run Code Online (Sandbox Code Playgroud)

只要我们使用await事件循环,就会从该点获取控制流来处理其他事情,直到IO完成为止.但可悲的是这些代码是不是在这个时候用,因为我们没有一个coroutined版本socket.sendtosocket.recvfromasyncio.

我们可以实现的是使用传输协议回调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()
Run Code Online (Sandbox Code Playgroud)

await因为回调不是协同程序,所以我们不能在那里进行协程,并且使用上面的阻塞IO调用会停止回调中的控制流并阻止循环处理任何其他事件,直到IO完成为止

另一个推荐的实现思路是Futuredata_received函数中创建一个对象,将它添加到事件循环中,并在Protocol类中存储任何所需的状态变量,然后显式地将控制返回给循环.虽然这可行,但它确实创建了许多复杂的代码,在协程版本中它们不需要以任何方式.

此外,我们还有一个使用非阻塞套接字和add_reader处理UDP套接字的示例.但是与coroutine-version的几行相比,代码看起来仍然很复杂.

我想说的是,协程是一个非常好的设计,可以在一个单独的线程中利用并发的力量,同时还有一个非常简单的设计模式,可以节省智能和不必要的代码行,但是获得关键部分它在我们的asyncio标准库中确实缺乏UDP网络.

你们怎么看待这件事?

此外,如果对于支持这种用于UDP网络的API的第三方库有任何其他建议,我将非常感谢我的课程项目.我发现Bluelet非常像这样的东西,但似乎没有积极维护.

编辑:

看来这个PR确实实现了这个功能但被asyncio开发人员拒绝了.开发人员声称可以使用create_datagram_endpoint()协议传输API 实现所有功能.但正如我上面所讨论的那样,协程API相比于在许多用例使用回调API简单的力量,实在是不幸的是,我们不UDP有这些.

use*_*342 8

不提供基于流的 API 的原因是因为流在回调之上提供排序,而 UDP 通信本质上是无序的,因此两者从根本上是不兼容的。

但这并不意味着您不能从回调中调用协程——实际上这很容易!从EchoServerProtocol示例开始,您可以执行以下操作:

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)
Run Code Online (Sandbox Code Playgroud)

这里datagram_received启动了你的handle_income_packet协程,它可以自由地等待任意数量的协程。由于协程在“后台”运行,事件循环在任何时候都不会被阻塞并datagram_received立即返回,正如预期的那样。