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