我今晚在浏览python asyncio模块文档,以寻找我的类(class)项目的一些想法,但是很快我发现python的标准aysncio模块可能缺少功能。

查看文档,您会发现有一个基于回调的API和一个基于协程的API。回调API可以用于构建UDP和TCP应用程序,而协程API似乎只能用于构建TCP应用程序,因为它利用了流样式API。

这确实给我带来了一个问题,因为我一直在寻找用于UDP网络的基于协程的API,尽管我确实发现asyncio支持基于底层协程的套接字方法(例如 sock_recv sock_sendall ),但是对于UDP网络,至关重要的API,recvfromsendto不存在。

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

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。但是遗憾的是,这些代码目前无法使用而不是,因为我们在socket.sendto中没有相关联的socket.recvfromasyncio版本。

我们可以在其中实现的方法是使用传输协议(protocol)回调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()

我们不能在此处await一个协程,因为回调不是协程,并且使用像上面这样的阻塞IO调用将使回调中的控制流停滞并阻止循环处理任何其他事件,直到IO完成为止

另一个推荐的实现想法是在Future函数中创建一个data_received对象,将其添加到事件循环中,并将任何需要的状态变量存储在Protocol类中,然后将控制权显式返回到循环中。尽管这可行,但它确实创建了许多复杂的代码,而在协程版本中,它们根本不需要任何代码。

还有here,我们有一个使用非阻塞套接字和add_reader处理UDP套接字的示例。但是,与协程版本的几行代码相比,该代码看起来仍然很复杂。

我要说的是,协程是一个非常好的设计,可以在单个线程中利用并发功能,同时还具有一种非常直接的设计模式,可以节省人力和不必要的代码行,但是这是获得关键的部分我们的asyncio标准库中确实缺少适用于UDP网络的功能。

你们对此怎么看?

另外,如果对于支持此类UDP网络API的第三方库还有其他建议,我将非常感谢我的类(class)项目。我发现Bluelet很像这样的事情,但是似乎没有得到积极维护。

编辑:

看来PR确实实现了此功能,但被asyncio开发人员拒绝了。开发人员声称可以使用协议(protocol)传输API create_datagram_endpoint()来实现所有功能。但是,正如我上面所讨论的,与在许多用例中使用回调API相比,协程API具有简单性的功能,实在令人遗憾的是,我们在UDP中没有这些。

最佳答案

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

但这都不意味着您不能从回调中调用协程-实际上非常简单!从 EchoServerProtocol example开始,您可以执行以下操作:

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_received启动您的handle_income_packet协程,可以免费等待任何数量的协程。由于协程在“后台”运行,因此事件循环在任何时候都不会被阻塞,并且datagram_received如预期的那样立即返回。

关于python - python的asyncio是否支持UDP网络的基于协程的API?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48621360/

10-13 06:44