用twisted的方式实现前面的内容html
最终咱们将使用twisted的方式来从新实现咱们前面的异步模式客户端。不过,首先咱们先稍微写点简单的twisted程序来认识一下twisted。python
最最简单的twisted程序就是下面的代码,其在twisted-intro目录中的basic-twisted/simple.py中。react
from twisted.internet import reactor reactor.run()
能够用下面的命令来运行它:linux
python basic-twisted/simple.py
正如在第二部分所说的那样,twisted是实现了Reactor模式的,所以它必然会有一个对象来表明这个reactor或者说是事件循环,而这正是twisted的核心。上面代码的第一行引入了reactor,第二行开始启动事件循环。git
这个程序什么事情也不作。除非你经过ctrl+c来终止它,不然它会一直运行下去。正常状况下,咱们须要给出事件循环或者文件描述符来监视I/O(链接到某个服务器上,好比说咱们那个诗歌服务器)。后面咱们会来介绍这部份内容,如今这里的reactor被卡住了。值得注意的是,这里并非一个在不停运行的简单循环。若是你在桌面上有个CPU性能查看器,能够发现这个循环体不会带来任何性能损失。实际上,这个reactor被卡住在第二部分图5的最顶端,等待永远不会到来的事件发生(更具体点说是一个调用select函数,却没有监视任何文件描述符)。github
下面咱们会让这个程序丰富起来,不过事先要说几个结论:编程
1.Twisted的reactor只有经过调用reactor.run()来启动。服务器
2.reactor循环是在其开始的进程中运行,也就是运行在主进程中。网络
3.一旦启动,就会一直运行下去。reactor就会在程序的控制下(或者具体在一个启动它的线程的控制下)。框架
4.reactor循环并不会消耗任何CPU的资源。
5.并不须要显式的建立reactor,只须要引入就OK了。
最后一条须要解释清楚。在Twisted中,reactor是Singleton(也是一种模式),即在一个程序中只能有一个reactor,而且只要你引入它就相应地建立一个。上面引入的方式这是twisted默认使用的方法,固然了,twisted还有其它能够引入reactor的方法。例如,可使用twisted.internet.pollreactor
中的系统调用来poll
来代替
select
方法
。
若使用其它的
reactor
,须要在引入
twisted.internet.reactor
前安装它。下面是安装
pollreactor的方法
:
from twisted.internet import pollreactor pollreactor.install()
若是你没有安装其它特殊的
reactor
而引入了
twisted.internet.reactor
,那么
Twisted
会为你安装
selectreactor
。正由于如此,习惯性作法不要在最顶层的模块内引入
reactor
以免安装默认
reactor
,而是在你要使用
reactor
的区域内安装。
下面
是使用
pollreactor
重写上上面的程序,能够在
basic-twisted/simple-poll.py文件中找到中找到:
from twited.internet import pollreactor pollreactor.install() from twisted.internet import reactor reactor.run()
上面这段代码一样没有作任何事情。
后面咱们都会只使用默认的
reactor
,就单纯为了学习来讲 ,全部的不一样的
reactor
作的事情都同样。
你好,
Twisted
咱们得用
Twisted
来作什么吧。下面这段代码在
reactor
循环开始后向终端打印一条消息:
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()
这段代码能够在
basic-twisted/hello.py中找到。运行它,会获得以下结果:
Starting the reactor. Hello from the reactor loop! Lately I feel like I'm stuck in a rut.
仍然须要你手动来关掉程序,由于它在打印完毕后就又卡住了。
值得注意的是,
hello
函数是在
reactor
启动后被调用的。这意味是
reactor
调用的它,也就是说
Twisted
在调用咱们的函数。咱们经过调用
reactor
的
callWhenRunning
函数,并传给它一个咱们想调用函数的引用来实现
hello
函数的调用。固然,咱们必须在启动
reactor
以前完成这些工做。
咱们使用回调来描述
hello
函数的引用。回调
实际上就是交给
Twisted
(或者其它框架)的一个函数引用,这样
Twisted
会在合适的时间调用这个函数引用指向的函数,具体到这个程序中,是在
reactor
启动的时候调用。因为
Twisted
循环是独立于咱们的代码,咱们的业务代码与
reactor
核心代码的绝大多数交互都是经过使用
Twisted
的
APIs
回调咱们的业务函数来实现的。
咱们能够经过下面这段代码来观察
Twisted
是如何调用咱们代码的:
import traceback def stack(): print 'The python stack:' traceback.print_stack() from twisted.internet import reactor reactor.callWhenRunning(stack) reactor.run()
这段代码的文件是
basic-twisted/stack.py。不出意外,它的输出是:
The python stack: ... reactor.run() <-- This is where we called the reactor ... ... <-- A bunch of Twisted function calls ... traceback.print_stack() <-- The second line in the stack function
不用考虑这其中的若干
Twisted
自己的函数。只须要关心
reactor.run()
与咱们本身的函数调用之间的关系便可。
有关回调的一些其它说明:
Twisted
并非惟一使用回调的框架。许多历史悠久的框架都已在使用它。诸多
GUI
的框架也是基于回调来实现的,如
GTK
和
QT
。
交互式程序的编程人员特别喜欢回调。也许喜欢到想嫁给它。也许已经这样作了。但下面这几点值得咱们仔细考虑下:
1.reactor
模式是单线程的。
2.
像
Twisted
这种交互式模型已经实现了
reactor
循环,意味无需咱们亲自去实现它。
3.
咱们仍然须要框架来调用咱们本身的代码来完成业务逻辑。
4.
由于在单线程中运行,要想跑咱们本身的代码,必须在
reactor
循环中调用它们。
5.reactor
事先并不知道调用咱们代码的哪一个函数
这样的话,回调并不只仅是一个可选项,而是游戏规则的一部分。
图
6
说明了回调过程当中发生的一切:
图
6 reactor
启用回调
图
6
揭示了回调中的几个重要特性:
1.
咱们的代码与
Twisted
代码运行在同一个进程中。
2.
当咱们的代码运行时,
Twisted
代码是处于暂停状态的。
3.
一样,当
Twisted
代码处于运行状态时,咱们的代码处于暂停状态。
4.reactor
事件循环会在咱们的回调函数返回后恢复运行。
在一个回调函数执行过程当中,实际上
Twisted
的循环
是
被有效地阻塞在咱们的代码上
的
。所以,所以咱们应该确保回调函数不要浪费时间(尽快返回)。特别须要强调
的是,咱们应该尽可能避免在回调函数中使用会阻塞
I/O
的函数。不然,咱们将失去全部使用
reactor
所带来的优点。
Twisted
是不会采起特殊的预防措施来防止咱们使用可阻塞的代码的,这须要咱们本身来确保上面的状况不会发生。正如咱们实际所看到的同样,对于普通网络
I/O
的例子,因为咱们让
Twisted
替咱们完成了异步通讯,所以咱们无需担忧上面的事情发生。
其它也可能会产生阻塞的操做是读或写一个非
socket
文件描述符(如管道)或者是等待一个子进程完成。
如何从阻塞转换到非阻塞操做取决你具体的操做是什么,可是也有一些
Twisted APIs
会帮助你实现转换。值得注意的是,不少标准的
Python
方法没有办法转换为非阻塞方式。例如,
os.system
中的不少方法会在子进程完成前一直处于阻塞状态。这也就是它工做的方式。因此当你使用
Twisted
时,避
开
使用
os.system
。
退出
Twisted
原来咱们可使用
reactor
的
stop
方法来中止
Twisted
的
reactor
。可是一旦
reactor
中止就没法再启动了。(
Dave
的意思是,中止就退出程序了),所以只有在你想退出程序时才执行这个操做。
下面是退出代码,代码文件是
basic-twisted/countdown.py:
class Countdown(object): counter = 5 def count(self): from twisted.internet import reactor if self.counter == 0: reactor.stop() else: print self.counter, '...' self.counter -= 1 reactor.callLater(1, self.count) from twisted.internet import reactor reactor.callWhenRunning(Countdown().count) print 'Start!' reactor.run() print 'Stop!'
在这个程序中使用了
callLater
函数为
Twisted
注册了一个回调函数。
callLater
中的第二个参数是回调函数,第一个则是说明你但愿在未来几秒钟时执行你的回调函数。那
Twisted
如何来在指定的时间执行咱们安排好的的回调函数。因为程序并无监放任何文件描述符,为何它没有像前那些程序那样卡在
select
循环上?
select
函数,或者其它相似的函数,一样会接纳一个超时参数。若是在只提供一个超时参数值而且没有可供
I/O
操做的文件描述符而超时时间到时,
select
函数一样会返回。所以,若是设置一个
0
的超时参数,那么会无任何阻塞地当即检查全部的文件描述符集。
你能够将超时做为图
5
中循环等待中的一种事件来看待。而且
Twisted
使用超时事件来确保那些经过
callLater
函数注册的延时回调在指定的时间执行。或者更确切的说,在指定时间的先后会执行。若是一个回调函数执行时间过长,那么下面的延时回调函数可能会被相应的后延执行。
Twisted
的
callLater
机制并不为硬实时系统提供任什么时候间上的保证。
下面是上面程序的输出:
Start! 5 ... 4 ... 3 ... 2 ... 1 ... Stop!
捕获
它,
Twisted
因为
Twisted
常常会在回调中结束调用咱们的代码,所以你可能会想,若是咱们的回调函数中出现异常会发生什么情况。(
Dave
的意思是说,在结束咱们的回调函数后会再次回到
Twisted
代码中,若在咱们的回调中发生异常,那是否是异常会跑到
Twisted
代码中,而形成不可想象的后果 )让咱们来试试,在
basic-twisted/exception.py中的程序会在一个回调函数中引起一个异常,可是这不会影响下一个回调:
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()
当你在命令行中运时,会有以下的输出:
Starting the reactor. Traceback (most recent call last): ... # I removed most of the traceback exceptions.Exception: I fall down. But I get up again.
注意
,尽管咱们看到了因第一个回调函数引起异常而出现的跟踪栈,第二个回调函数依然可以执行。若是你将
reactor.stop()
注释掉的话,程序会继续运行下去。因此说,
reactor
并不会由于回调函数中出现失败(虽然它会报告异常)而中止运行。
网络服务器一般须要这种健壮的软件。它们一般不但愿因为一个随机的
Bug
致使崩溃。
也并非说当咱们发现本身的程序内部有问题时,就垂头丧气。只是想说
Twisted
可以很好的从失败的回调中返回并继续执行。
请继续讲解诗歌服务器
如今,咱们已经准备好利用
Twisted
来搭建咱们的诗歌服务器。在第
4
部分,咱们会实现咱们的异步模式的诗歌服务器的
Twisted
版。