用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
版。