最終我們將使用twisted的方式來重新實現(xiàn)我們前面的異步模式客戶端。不過,首先我們先稍微寫點簡單的twisted程序來認(rèn)識一下twisted。
最最簡單的twisted程序就是下面的代碼,其在twisted-intro目錄中的basic-twisted/simple.py中。
from twisted.internet import reactor
reactor.run()
可以用下面的命令來運行它:
python basic-twisted/simple.py
正如在第二部分所說的那樣,twisted實現(xiàn)了Reactor模式,因此它必然會有一個對象來代表這個reactor或者說是事件循環(huán),而這正是twisted的核心。上面代碼的第一行引入了reactor,第二行開始啟動事件循環(huán)。
這個程序什么事情也不做。除非你通過ctrl+c來終止它,否則它會一直運行下去。正常情況下,我們需要給出事件循環(huán)或者文件描述符來監(jiān)視I/O(連接到某個服務(wù)器上,比如說我們那個詩歌服務(wù)器)。后面我們會來介紹這部分內(nèi)容,現(xiàn)在這里的reactor被卡住了。值得注意的是,這里并不是一個在不停運行的簡單循環(huán)。如果你在桌面上有個CPU性能查看器,可以發(fā)現(xiàn)這個循環(huán)體不會帶來任何性能損失。實際上,這個reactor被卡住在第二部分圖5的最頂端,等待永遠(yuǎn)不會到來的事件發(fā)生(更具體點說是一個調(diào)用select函數(shù),卻沒有監(jiān)視任何文件描述符)。
下面我們會讓這個程序豐富起來,不過事先要說幾個結(jié)論:
最后一條需要解釋清楚。在Twisted中,reactor是Singleton模式,即在一個程序中只能有一個reactor,并且只要你引入它就相應(yīng)地創(chuàng)建一個。上面引入的方式是twisted默認(rèn)使用的方法,當(dāng)然了,twisted還有其它可以引入reactor的方法。例如,可以使用twisted.internet.pollreactor來調(diào)用poll代替select方法。
若使用其它的reactor,需要在引入twisted.internet.reactor前安裝它。下面是安裝pollreactor的方法:
from twisted.internet import pollreactor
pollreactor.install()
如果你沒有安裝其它特殊的reactor而引入了twisted.internet.reactor,那么Twisted會為你安裝selectreactor。正因為如此,習(xí)慣性做法不要在最頂層的模塊內(nèi)引入reactor以避免安裝默認(rèn)reactor,而是在你要使用reactor的區(qū)域內(nèi)安裝。
下面是使用 pollreactor重寫上面的程序,可以在basic-twisted/simple-poll.py文件中找到:
from twited.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()
上面這段代碼同樣沒有做任何事情。
后面我們都會只使用默認(rèn)的reactor,就單純?yōu)榱藢W(xué)習(xí)來說 ,所有的reactor做的事情都一樣。
我們得用Twisted來做什么吧。下面這段代碼在reactor循環(huán)開始后向終端打印一條消息:
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中找到。運行它,會得到如下結(jié)果:
Starting the reactor.
Hello from the reactor loop!
Lately I feel like I'm stuck in a rut.
仍然需要你手動來關(guān)掉程序,因為它在打印完畢后就又卡住了。
值得注意的是,hello函數(shù)是在reactor啟動后被調(diào)用的。這意味是reactor調(diào)用的它,也就是說Twisted在調(diào)用我們的函數(shù)。我們通過調(diào)用reactor的callWhenRunning函數(shù),并傳給它一個我們想調(diào)用函數(shù)的引用來實現(xiàn)hello函數(shù)的調(diào)用。當(dāng)然,我們必須在啟動reactor之前完成這些工作。
我們使用回調(diào)來描述hello函數(shù)的引用。回調(diào)實際上就是交給Twisted(或者其它框架)的一個函數(shù)引用,這樣Twisted會在合適的時間調(diào)用這個函數(shù)引用指向的函數(shù),具體到這個程序中,是在reactor啟動的時候調(diào)用。由于Twisted循環(huán)是獨立于我們的代碼,我們的業(yè)務(wù)代碼與reactor核心代碼的絕大多數(shù)交互都是通過使用Twisted的APIs回調(diào)我們的業(yè)務(wù)函數(shù)來實現(xiàn)的。
我們可以通過下面這段代碼來觀察Twisted是如何調(diào)用我們代碼的:
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本身的函數(shù)。只需要關(guān)心reactor.run()與我們自己的函數(shù)調(diào)用之間的關(guān)系即可。
有關(guān)回調(diào)的一些其它說明:
Twisted并不是唯一使用回調(diào)的框架。許多歷史悠久的框架都已在使用它。諸多GUI的框架也是基于回調(diào)來實現(xiàn)的,如GTK和QT。交互式程序的編程人員特別喜歡回調(diào)。也許喜歡到想嫁給它。也許已經(jīng)這樣做了。但下面這幾點值得我們仔細(xì)考慮下:
這樣的話,回調(diào)并不僅僅是一個可選項,而是游戲規(guī)則的一部分。
圖6說明了回調(diào)過程中發(fā)生的一切:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p03_reactor-callback.png" alt="" />
圖6揭示了回調(diào)中的幾個重要特性:
在一個回調(diào)函數(shù)執(zhí)行過程中,實際上Twisted的循環(huán)是被有效地阻塞在我們的代碼上的。因此,我們應(yīng)該確?;卣{(diào)函數(shù)不要浪費時間(盡快返回)。特別需要強調(diào)的是,我們應(yīng)該盡量避免在回調(diào)函數(shù)中使用會阻塞I/O的函數(shù)。否則,我們將失去所有使用reactor所帶來的優(yōu)勢。Twisted是不會采取特殊的預(yù)防措施來防止我們使用可阻塞的代碼的,這需要我們自己來確保上面的情況不會發(fā)生。正如我們實際看到的一樣,對于普通網(wǎng)絡(luò)I/O的例子,由于我們讓Twisted替我們完成了異步通信,因此我們無需擔(dān)心上面的事情發(fā)生。
其它也可能會產(chǎn)生阻塞的操作是讀或?qū)懸粋€非socket文件描述符(如管道)或者是等待一個子進程完成。
如何從阻塞轉(zhuǎn)換到非阻塞操作取決你具體的操作是什么,但是也有一些Twisted APIs會幫助你實現(xiàn)轉(zhuǎn)換。值得注意的是,很多標(biāo)準(zhǔn)的Python方法沒有辦法轉(zhuǎn)換為非阻塞方式。例如,os.system中的很多方法會在子進程完成前一直處于阻塞狀態(tài),這也就是它工作的方式。所以當(dāng)你使用Twisted時,避開使用os.system。
原來我們可以使用reactor的stop方法來停止Twisted的reactor。但是一旦reactor停止就無法再啟動了。(Dave的意思是,停止就退出程序了),因此只有在你想退出程序時才執(zhí)行這個操作。
下面是退出代碼,代碼文件是basic-twisted/countdown.py:
class Countdown(object):
counter = 5
def count(self):
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函數(shù)為Twisted注冊了一個回調(diào)函數(shù)。callLater中的第二個參數(shù)是回調(diào)函數(shù),第一個則是說明你希望在將來幾秒鐘時執(zhí)行你的回調(diào)函數(shù)。那Twisted如何來在指定的時間執(zhí)行我們安排好的的回調(diào)函數(shù)。由于程序并沒有監(jiān)聽任何文件描述符,為什么它沒有像前那些程序那樣卡在select循環(huán)上?select函數(shù),或者其它類似的函數(shù),同樣會接納一個超時參數(shù)。如果在只提供一個超時參數(shù)值并且沒有可供I/O操作的文件描述符而超時時間到時,select函數(shù)同樣會返回。因此,如果設(shè)置一個0的超時參數(shù),那么會無任何阻塞地立即檢查所有的文件描述符集。
你可以將超時作為圖5中循環(huán)等待中的一種事件來看待。并且Twisted使用超時事件來確保那些通過callLater函數(shù)注冊的延時回調(diào)在指定的時間執(zhí)行。或者更確切的說,在指定時間的前后會執(zhí)行。如果一個回調(diào)函數(shù)執(zhí)行時間過長,那么下面的延時回調(diào)函數(shù)可能會被相應(yīng)的后延執(zhí)行。Twisted的callLater機制并不為硬實時系統(tǒng)提供任何時間上的保證。
下面是上面程序的輸出:
Start!
5 ...
4 ...
3 ...
2 ...
1 ...
Stop!
由于Twisted經(jīng)常會在回調(diào)中結(jié)束調(diào)用我們的代碼,因此你可能會想,如果我們的回調(diào)函數(shù)中出現(xiàn)異常會發(fā)生什么狀況。(Dave的意思是說,在結(jié)束我們的回調(diào)函數(shù)后會再次回到Twisted代碼中,若在我們的回調(diào)中發(fā)生異常,那是不是異常會跑到Twisted代碼中,而造成不可想象的后果 )讓我們來試試,在basic-twisted/exception.py中的程序會在一個回調(diào)函數(shù)中引發(fā)一個異常,但是這不會影響下一個回調(diào):
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()
當(dāng)你在命令行中運時,會有如下的輸出:
Starting the reactor. Traceback (most recent call last):
... # I removed most of the traceback
exceptions.Exception: I fall down.
But I get up again.
注意,盡管我們看到了因第一個回調(diào)函數(shù)引發(fā)異常而出現(xiàn)的跟蹤棧,第二個回調(diào)函數(shù)依然能夠執(zhí)行。如果你將reactor.stop()注釋掉的話,程序會繼續(xù)運行下去。所以說,reactor并不會因為回調(diào)函數(shù)中出現(xiàn)失敗(雖然它會報告異常)而停止運行。
網(wǎng)絡(luò)服務(wù)器通常需要這種健壯的軟件。它們通常不希望由于一個隨機的Bug導(dǎo)致崩潰。也并不是說當(dāng)我們發(fā)現(xiàn)自己的程序內(nèi)部有問題時,就垂頭喪氣。只是想說Twisted能夠很好的從失敗的回調(diào)中返回并繼續(xù)執(zhí)行。
現(xiàn)在,我們已經(jīng)準(zhǔn)備好利用Twisted來搭建我們的詩歌服務(wù)器。在第4部分,我們會實現(xiàn)我們的異步模式的詩歌服務(wù)器的Twisted版。
本部分原作參見: dave http://krondo.com/?p=1333
本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100pzhf.html