(轉) Twisted : 第三部分:開始認識Twisted

twisted的方式實現前面的內容html

最終咱們將使用twisted的方式來從新實現咱們前面的異步模式客戶端。不過,首先咱們先稍微寫點簡單的twisted程序來認識一下twistedpython

最最簡單的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.Twistedreactor只有經過調用reactor.run()來啓動。服務器

2.reactor循環是在其開始的進程中運行,也就是運行在主進程中。網絡

3.一旦啓動,就會一直運行下去。reactor就會在程序的控制下(或者具體在一個啓動它的線程的控制下)。框架

4.reactor循環並不會消耗任何CPU的資源。

5.並不須要顯式的建立reactor,只須要引入就OK了。

最後一條須要解釋清楚。在Twisted中,reactorSingleton(也是一種模式),即在一個程序中只能有一個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在調用咱們的函數。咱們經過調用reactorcallWhenRunning函數,並傳給它一個咱們想調用函數的引用來實現hello函數的調用。固然,咱們必須在啓動reactor以前完成這些工做。

咱們使用回調來描述hello函數的引用。回調實際上就是交給Twisted(或者其它框架)的一個函數引用,這樣Twisted會在合適的時間調用這個函數引用指向的函數,具體到這個程序中,是在reactor啓動的時候調用。因爲Twisted循環是獨立於咱們的代碼,咱們的業務代碼與reactor核心代碼的絕大多數交互都是經過使用TwistedAPIs回調咱們的業務函數來實現的。

咱們能夠經過下面這段代碼來觀察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的框架也是基於回調來實現的,如GTKQT

交互式程序的編程人員特別喜歡回調。也許喜歡到想嫁給它。也許已經這樣作了。但下面這幾點值得咱們仔細考慮下:

1.reactor模式是單線程的。

2.Twisted這種交互式模型已經實現了reactor循環,意味無需咱們親自去實現它。

3.咱們仍然須要框架來調用咱們本身的代碼來完成業務邏輯。

4.由於在單線程中運行,要想跑咱們本身的代碼,必須在reactor循環中調用它們。

5.reactor事先並不知道調用咱們代碼的哪一個函數

這樣的話,回調並不只僅是一個可選項,而是遊戲規則的一部分。

6說明了回調過程當中發生的一切:

第三部分:初步認識Twisted

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

原來咱們可使用reactorstop方法來中止Twistedreactor。可是一旦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函數註冊的延時回調在指定的時間執行。或者更確切的說,在指定時間的先後會執行。若是一個回調函數執行時間過長,那麼下面的延時回調函數可能會被相應的後延執行。TwistedcallLater機制並不爲硬實時系統提供任什麼時候間上的保證。

下面是上面程序的輸出:

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

相關文章
相關標籤/搜索