简体   繁体   English

如何将FileTransport干净地添加到Asyncio?

[英]How to add a FileTransport cleanly to Asyncio?

I'm writing an application which reads text data and acts on it. 我正在编写一个读取文本数据并对其进行操作的应用程序。 The text data could come from a TCP port, or from a text file (which contains data earlier read from the TCP port and archived). 文本数据可以来自TCP端口,也可以来自文本文件(该文件包含先前从TCP端口读取并存档的数据)。 I'm writing it in Python 3, and using asyncio seems like the obvious tool to use. 我正在用Python 3编写它,并且使用asyncio似乎是显而易见的工具。

It's straightforward to use the Streams API open_connection() to open the TCP port and read from it. 使用Streams API open_connection()打开TCP端口并从中读取数据很简单。 The asyncio architecture has the concept a Transport and a Protocol for the lower and upper layers of input-output. 异步体系结构的概念是针对输入输出的下层和上层的传输协议 So, it seems I should implement a Transport for reading text from a file, and pass it to the Protocol. 因此,似乎我应该实现一个传输来从文件中读取文本,并将其传递给协议。 That will let me keep the rest of my application decoupled from whether the text data came from the TCP port or the file. 这将使我的应用程序其余部分与文本数据是来自TCP端口还是来自文件分离。

But I'm having a hard time figuring out how to tell asyncio to use my preferred Transport. 但是我很难弄清楚如何告诉asyncio使用我首选的Transport。

  • the Streams API open_connection() has a parameter list which is all about the TCP port Transport, with no way to specify a different Transport, much less parameters like file path. Streams API open_connection()具有一个与TCP端口Transport有关的参数列表,无法指定其他Transport,而文件路径等参数要少得多。
  • open_connection() turns around and calls loop.create_connection() . open_connection()转过身并调用loop.create_connection() This is just as specialised for the TCP Port Transport. 正如专门用于TCP端口传输一样。 Still now way to provide a different Transport. 现在仍然可以提供不同的运输方式。
  • The implementation of loop.create_connection() gets its Transport object from either self._make_ssl_transport() or self._make_socket_transport() . loop.create_connection()的实现从self._make_ssl_transport()self._make_socket_transport()获取其Transport对象。 These have alternative implementations in asyncio.selector_events.BaseSelectorEventLoop and asyncio.proactor_events.BaseProactorEventLoop , so we are clearly past the point where a File Transport ought to have been selected. 这些在asyncio.selector_events.BaseSelectorEventLoopasyncio.proactor_events.BaseProactorEventLoop具有替代实现,因此我们显然已经超过了应该选择文件传输的地步。

Am I missing some place where asyncio lets me tell it what Transport to use? 我是否缺少某个地方让asyncio告诉我要使用哪种传输方式? Or is asyncio really coded down to its roots to use its own TCP port and UDP datagram Transports, and nothing else? 还是将asyncio真正地根深蒂固地编码为使用其自己的TCP端口和UDP数据报传输器,仅此而已?

If I want to allow the possibility of using my own Transport with asyncio , it looks like I have to extend the event loop, or write more a flexible alternative create_connection() that is coded to a particular event loop implementation. 如果我想允许将自己的Transport与asyncio一起使用 ,则看起来我必须扩展事件循环,或者编写更多灵活的替代create_connection() ,将其编码为特定的事件循环实现。 That seems like a lot of work, and vulnerable to changes in the implementation. 这似乎是一项艰巨的工作,并且容易受到实施更改的影响。

Or, is it a foolish errand to handle file input with a Transport? 还是用传输处理文件输入是愚蠢的事情? Should I instead structure my code to say: 我是否应该改写我的代码以说:

if (using_tcp_port): await asyncio.open_connection(....) else: completely_different_file_implementation(....)

According to the documentation of API create_connection() , it takes a protocol and creates a streaming transport, which is a TCP connection. 根据文档 APIcreate_connection()需要一个协议,并创建一个流式传输,这一个TCP连接。 So it is not supposed to be an API for custom transports. 因此,它不应该是用于自定义传输的API。

However, the idea to reuse the same protocol for either TCP transports or custom file transports is valid. 但是,对于TCP传输或自定义文件传输重用相同协议的想法是有效的。 It won't be a "completely different implementation", but at least not using create_connection() . 它不会是“完全不同的实现”,但至少不会使用create_connection() Let's assume it is read_file() : 假设它是read_file()

def my_protocol_factory():
    return your_protocol

if using_tcp_port:
    transport, protocol = await loop.create_connection(my_protocol_factory, host, port)
else:
    transport, protocol = await read_file(loop, my_protocol_factory, path_to_file)

Then you would have something like this: 然后,您将获得以下内容:

from asyncio import transports

import aiofiles  # https://github.com/Tinche/aiofiles


def read_file(loop, protocol_factory, path):
    protocol = protocol_factory()
    transport = FileTransport(path, loop)
    transport.set_protocol(protocol)
    return transport, protocol


class FileTransport(transports.ReadTransport):
    def __init__(self, path, loop):
        super().__init__()
        self._path = path
        self._loop = loop
        self._closing = False

    def is_closing(self):
        return self._closing

    def close(self):
        self._closing = True

    def set_protocol(self, protocol):
        self._protocol = protocol
        self._loop.create_task(self._do_read())

    def get_protocol(self):
        return self._protocol

    async def _do_read(self):
        try:
            async with aiofiles.open(self._path) as f:
                self._loop.call_soon(self._protocol.connection_made, self)
                async for line in f:
                    self._loop.call_soon(self._protocol.data_received, line)
                    if self._closing:
                        break
                self._loop.call_soon(self._protocol.eof_received)
        except Exception as ex:
            self._loop.call_soon(self._protocol.connection_lost, ex)
        else:
            self._loop.call_soon(self._protocol.connection_lost, None)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM