python网络编程Twisted02 Twisted基础和Reactor方法

发布时间 2023-07-28 15:05:19作者: 岗岗好
 

Twisted第三方文档:https://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/
Twisted官方文档:https://twistedmatrix.com/trac/wiki/Documentation

1、Twisted框架介绍

  • twisted是一个完整的事件驱动的网络框架,利用它既能使用也能开发完整的异步网络应用程序和协议。
  • twisted提供了大量的支持来建立完整的系统,包括网络协议、线程、安全性和身份验证、聊天/IM、DBM及RDBMS数据库集成、Web/因特网、电子邮件、命令行参数、GUI集成工具包等。
  • twisted不是Python标准库的一部分,所以必须单独下载并安装它。
1
pip install twisted

2、Twisted的启动

1、Twisted的reactor对象

  • Twisted是反应器模式的一个实现,因此包含一个代表反应器或事件循环的对象(即reactor对象),它是任何Twisted程序的核心。
  • 在Twisted中,反应器基本上是一个Singleton。只有一个反应器对象,它是在导入时隐式创建的。如果你打开twisted.internet包中的reactor模块,你会发现只有很少的代码。实际的实现是在其他文件中(例如,参见twisted.internet.selectreactor)。

示例:最简单的Twisted程序

  • 这个程序只是坐在那里什么都不做。你必须按Control-C来停止它,否则它会永远坐在那里。通常,我们会给循环一个或多个文件描述符,以便监控I/O。
  • 请注意,这不是一个不断循环的繁忙循环。如果你的屏幕上恰好有一个CPU仪表,你不会看到这种技术上的无限循环引起的任何峰值。事实上,我们的程序根本没有使用任何CPU。相反,反应器停留在循环的顶部(上一章的图1),等待一个永远不会出现的事件(具体来说,等待一个没有文件描述符的select调用)。
1
2
3
4
5
6
#程序路径D:\twisted-intro-master3\basic-twisted\simple.py
from twisted.internet import reactor    #导入reactor对象
reactor.run()                           #reactor开始运行循环
 
#运行程序
PS C:\Users\root> python3 D:\twisted-intro-master3\basic-twisted\simple.py

2、reactor对象的总结

  • Twisted的反应器循环只有接到指令才会启动。可以通过调用reactor.run()来启动它。
  • 反应器循环在启动它的一个线程中运行。在本例中,它运行在主线程中(也是唯一的)。
  • 一旦循环开始,它就会继续。反应器现在已经“控制”了程序(或者是启动它的特定线程)。
  • 如果它没有任何事情要做,反应器循环不消耗CPU。
  • 反应器不是显式创建的,只是导入。

3、其他reactor对象

  • Twisted实际上包含多个反应器实现。select调用只是等待文件描述符的一种方法。Twisted包括几个使用不同方法的反应器实现。例如,twisted.internet.pollreactor使用poll系统调用而不是select。
  • 要使用一个特定的反应器,你必须在导入twisted.internet.reactor之前安装它。下面是如何安装pollreactor:
1
2
from twisted.internet import pollreactor
pollreactor.install()
  • 如果您在导入twisted.internet.reactor前没有先安装特定的reactor实现,那么Twisted将为您安装默认的reactor。默认的reactor取决于您所使用的操作系统和Twisted版本。出于这个原因,一般的做法是不要在模块的顶层导入反应器,以避免意外安装默认反应器

示例:使用pollreactor反应器重新实现第一个twisted程序

  • 这是一个什么都不做的轮询循环,而不是一个什么都不做的选择循环。
1
2
3
4
5
6
#程序路径D:\twisted-intro-master3\basic-twisted\simple-poll.py
from twisted.internet import pollreactor
pollreactor.install()
  
from twisted.internet import reactor
reactor.run()

3、Twisted的回调

  • Twisted不是唯一使用回调的反应器框架。较早的异步Python框架Medusa和asyncore也使用它们。GUI工具包GTK和QT也是如此,它们都像许多GUI框架一样,基于反应器循环。

示例:

  • 这是在反应器循环启动后将消息打印到终端窗口的一个Twisted程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def hello():
    print'Hello from the reactor loop!')
    print'Lately I feel like I\'m stuck in a rut.')
 
from twisted.internet import reactor
reactor.callWhenRunning(hello)
 
print'Starting the reactor.')
reactor.run()
 
<<<
Starting the reactor.
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.

1、使用术语“回调”来描述对hello函数的引用

  • 注意:hello函数在反应器开始运行后被调用。这意味着它由反应器本身调用,因此Twisted代码必须调用我们的代码。
    • 通过reactor对象调用callWhenRunning方法(回调)来引用我们希望Twisted调用的函数。当然,必须在启动反应器之前这样做。
  • 在反应器循环启动之后,Twisted将使用回调函数在适当的时间“回调我们的代码”
  • 由于Twisted的循环与我们的代码是分开的,所以反应器核心和我们的业务逻辑之间的大多数交互将从对我们使用各种API提供给Twisted的函数的回调开始。

2、反应器系统的开发者喜欢回调。但请思考:

  1. 反应器模式是单线程的。
  2. 像Twisted这样的反应式框架实现了reactor循环,因此我们的代码不必这样做。
  3. 我们的代码仍然需要被调用来实现我们的业务逻辑。
  4. 由于它在单线程中,因此反应器循环将不得不调用我们的代码。
  5. 反应器无法提前知道需要调用我们代码的哪一部分

3、回调期间发生的事情:

  1. 我们的回调代码与Twisted循环在同一线程中运行。
  2. 当我们的回调运行时,Twisted循环没有运行。
  3. 反之亦然。
  4. 当我们的回调返回时,反应器循环恢复。
  • 在回调期间,Twisted循环被我们的代码有效地“阻塞”。
    • 应该确保我们的回调代码不会浪费任何时间。
    • 特别是,应该避免在回调中执行阻塞的I/O调用。否则,将失去使用反应器模式的全部意义。
    • Twisted不会采取任何特别的预防措施来防止代码阻塞,我们只需要确保不这样做。
    • 正如我们最终将看到的,对于网络I/O的常见情况,我们不需要担心它,因为我们让Twisted为我们做异步通信。
  • 其他可能阻塞操作,包括从非套接字文件描述符(比如管道)读或写,或者等待子进程完成。从阻塞操作切换到非阻塞操作的方式具体取决于您正在做什么,但通常有一个Twisted API可以帮助您做到这一点。注意,许多标准Python函数无法切换到非阻塞模式。例如,该os.system函数将始终阻塞,直到子进程完成。因此,在使用Twisted时,你必须避免使用os.system支持Twisted API来启动子进程。

4、回调失败

示例:

  • 在一个回调中引发异常,但在另一个回调中表现正常
  • 请注意,第二个回调在第一个回调之后运行,即使我们看到了第一个引发的异常的回溯。
  • 如果您注释掉reactor.stop()调用,程序将永远运行。因此,即使我们的回调失败,反应器也会继续运行(尽管它会报告异常)。
1
2
3
4
5
6
7
8
9
10
11
12
def falldown():
    raise Exception('I fall down.')
def upagain():
    print('But I get up again.')
    reactor.stop()
 
from twisted.internet import reactor
reactor.callWhenRunning(falldown)
reactor.callWhenRunning(upagain)
 
print('Starting the reactor.')
reactor.run()

4、twisted的停止

  • 可以使用反应器的stop方法告诉Twisted反应器停止运行。但是一旦停止反应器就无法重新启动,因此通常只有在程序需要退出时才执行此操作。
  • 从Twisted8.2.0版本开始,只能启动(并因此停止)反应器一次。

示例:

  • 这个程序使用callLater API向Twisted注册一个回调。使用callLater,回调是第二个参数,第一个参数是你希望回调在未来运行的秒数。
  • 那么Twisted如何安排在正确的时间执行回调呢?既然这个程序不监听任何文件描述符,为什么它不会像其他程序一样在select循环中卡住呢?select调用以及其他类似的调用也接受一个可选的超时值。如果提供了超时值,并且在指定的时间内没有文件描述符为I/O做好准备,那么select调用将会返回。顺便提一下,通过传递0的超时值,您可以快速检查(或“轮询”)一组文件描述符,而完全不会阻塞。
  • Twisted使用超时来确保在callLater中注册的任何“定时回调”都能在正确的时间被调用。或者更确切地说,大约在正确的时间。如果另一个回调需要很长时间来执行,则定时回调可能会延迟超过其调度。Twisted的callLater机制无法提供硬实时系统所需的那种保证。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Countdown(object):
    counter = 5
    def count(self):
        if self.counter == 0:
            reactor.stop()
        else:
            print(self.counter, '...')
            self.counter -= 1
            reactor.callLater(1self.count)
 
from twisted.internet import reactor
reactor.callWhenRunning(Countdown().count)
 
print('Start!')
reactor.run()
print('Stop!')
 
<<<
Start!
5 ...
4 ...
3 ...
2 ...
1 ...
Stop!

5、Reactor的方法

1、Reactor必须实现的核心方法

twisted.internet.interfaces.IReactorCore接口文档

1、running = 

  • 一个布尔值,从启动到关闭期间为True,其余时间为False。

2、resolve(name, timeout)

  • 返回一个twisted.internet.defer.Deferred将解析主机名。

3、run()

  • 触发“startup”(启动)系统事件,将反应器移动到“running”(运行)状态,然后运行主循环,直到它被stop()或crash()停止。

4、stop()

  • 触发“shutdown”(关闭)系统事件,这会将反应器移动到“stopped”(停止)状态并导致reactor.run()退出。

5、crash()

  • 立即停止主循环,不触发任何系统事件。
  • 之所以这样命名,是因为这是一件非常“粗鲁”的事情;调用这个函数可能会丢失数据并使系统处于不一致的状态。然而,这是必要的,因为有时系统可能会陷入预关闭调用。

6、iterate(delay)

  • 运行主循环的I/O轮询功能一段时间。
  • 这在 UI 被“尽可能快地”绘制的应用程序中最有用,例如游戏。所有挂起的IDelayedCalls 都将被调用。
  • 在调用该方法之前,反应器必须已经启动(通过run()方法)。在最后一次调用这个方法之后(通过stop()方法),还必须手动停止它。这个方法是不可重入的:你不能递归地调用它;特别是,你不能在反应器运行时调用它。

7、fireSystemEvent(eventType)

  • 触发一个系统范围的事件。
  • 系统范围的事件包括'startup', 'shutdown'和'persist'。

8、addSystemEventTrigger(phase, eventType, callable, *args, **kwargs)

  • 添加一个在系统事件发生时要调用的函数。
  • Twisted中的每个“系统事件”,比如“startup启动”、“shutdown关闭”和“persist持续”,都有3个阶段:“before之前”、“during期间”和“after之后”(当然是按这个顺序)。这些事件将由反应器内部触发。
  • 此接口的实现者必须只实现此处描述的那些事件。
  • 为“before”阶段注册的回调可以返回None或Deferred。“during”阶段不会执行,直到“before”阶段的所有Deferred(延迟)都被触发。
  • 一旦“during”阶段运行,所有剩余的触发器都必须执行;它们的返回值必须被忽略。
    • phase:调用事件的时间——字符串'before','after'或'during'描述了相对于事件的执行调用它的时间。
    • eventType:这是一个描述事件类型的字符串
    • callable:关闭前要调用的对象。
    • args:调用它的参数。
    • kwargs:调用它的关键字参数。

9、removeSystemEventTrigger(triggerID)

  • 删除使用addSystemEventTrigger添加的触发器。

10、callWhenRunning(callable, *args, **kwargs)

  • 在反应器运行时调用一个函数。
  • 如果反应器还没有启动,那么当它启动时,可调用对象将被调度运行。否则,将立即调用可调用对象。

2、Reactor实现的时间方法

twisted.internet.interfaces.IReactorTime接口文档

1、seconds()

  • 以秒为单位获取当前时间。

2、callLater(delay, callable, *args, **kwargs)

  • 稍后调用函数。
    • delay:等待的秒数。(type: float)
    • callable:稍后要调用的可调用对象。(type: Callable[..., Any])
    • args:调用它的参数。(type: object)
    • kwargs:调用它的关键字参数。(type: object)

3、getDelayedCalls()

  • 检索所有当前安排的延迟呼叫。

3、Reactor实现的描述符的方法

twisted.internet.interfaces.IReactorFDSet

1、addReader(reader)

  • 将reader添加到文件描述符集,以获取读取事件。
    • reader:一个IReadDescriptor提供程序,它将被检查是否有读事件,直到它被removeReader从反应器中移除。(type: IReadDescriptor)

2、addWriter(writer)

  • 将writer添加到文件描述符集中,以获取写入事件。
    • writer:一个IWriteDescriptor提供程序,它将被检查是否有写事件,直到它被removeWriter从反应器中移除。(type: IWriteDescriptor)

3、removeReader(reader)

  • 移除先前用addReader添加的对象。

4、removeWriter(writer)

  • 移除先前用addWriter添加的对象

5、removeAll()

  • 删除所有readers和writers。
  • 不应该移除反应堆内部的连接(如wakaker)。
    • 返回值:被删除的IReadDescriptor和IWriteDescriptor提供程序的列表。 (type: List[Union[IReadDescriptor, IWriteDescriptor]]

6、getReaders()

  • 返回当前由反应器监控的输入事件的文件描述符列表。

7、getWriters()

  • 返回当前由反应器监控的输出事件的文件描述符列表。

 

6、习题

  • 更新countdown.py程序(即“4、twisted的停止“中的示例),使其有三个独立运行的计数器以不同的速率运行。当所有的计数器都结束时,停止反应堆。