第二部分用低效的詩歌服務器來啟發(fā)對Twisted機制的理解,現(xiàn)在我們可能要寫一些代碼。在開始之前,我們都做出一些必要的假設。
在展開討論前,我假設你已經(jīng)有過用Python寫同步程序的經(jīng)歷并且至少知道一點有關Python的Sockt編程的經(jīng)驗。如果你從沒有寫過Socket程序,或許你可以去看看Socket模塊的文檔,尤其是后面的示例代碼。如果你沒有用過Python的話,那后面的描述對你來說可能比看周易還痛苦。
我一般是在Linux上使用Twisted,這個系列的示例代碼也是在Linux下完成的。首先聲明的是我并沒有故意讓代碼失去平臺無關性,但我所講述的一些內(nèi)容確實可能僅僅適應于Linux和其它的類Unix(比如MAC OSX或FreeBSD)。WIndows是個奇怪詭異的地方(為什么這么評價Windows呢),如果你想嘗試在它上面學習這個系列,抱歉,如果出了問題,我無法提供任何幫助。
并且假設你已經(jīng)安裝了Python和Twisted。我所提供的示例代碼是基于Python2.5和Twisted8.2.0。
你可以在單機上運行所有的示例代碼,也可以在網(wǎng)絡系統(tǒng)上運行它們。但是為了學習異步編程的機制,單機上學習是比較理想的。
使用git工具來獲取Dave的最新示例代碼。在shell或其它命令行上輸入以下命令(假設已經(jīng)安裝git):
git clone git://github.com/jdavisp3/twisted-intro.git
下載結束后,解壓并進入第一層文件夾(你可以看到有個README文件)。
雖然CPU的處理速度遠遠快于網(wǎng)絡,但網(wǎng)絡的處理速度仍然比人腦快,至少比人類的眼睛快。因此,想通過網(wǎng)絡來獲得CPU的視角是很困難的,尤其是在單機的回路模式中數(shù)據(jù)流全速傳輸時,更是困難重重。
我們所需要的是一個慢速低效詩歌服務器,其用人為的可變延時來體現(xiàn)對結果的影響。畢竟服務器要提供點東西嘛,我們就提供詩歌好了。目錄下面有個子目錄專門存放詩歌用的。
最簡單的慢速詩歌服務器在blocking-server/slowpoetry.py中實現(xiàn)。你可用下面的方式來運行它。
python blocking-server/slowpoetry.py poetry/ecstasy.txt
上面這個命令將啟動一個阻塞的服務器,其提供"Ecstasy"這首詩?,F(xiàn)在我們來看看它的源碼內(nèi)容,正如你所見,這里面并沒有使用任何Twisted的內(nèi)容,只是最基本的Socket編程操作。它每次只發(fā)送一定字節(jié)數(shù)量的內(nèi)容,而每次中間延時一段時間。默認的是每隔0.1秒發(fā)送10個比特,你可以通過--delay和
--num-bytes參數(shù)來設置。例如每隔5秒發(fā)送50比特:
python blocking-server/slowpoetry.py --num-bytes 50 –-delay 5 poetry/ecstasy.txt
當服務器啟動時,它會顯示其所監(jiān)聽的端口號。默認情況下,端口號是在可用端口號池中隨機選擇的。你可能想使用固定的端口號,那么無需更改代碼,只需要在啟動命令中作下修改就OK了,如下所示:
python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt
如果你裝有netcat工具,可以用如下命令來測試你的服務器(也可以用telnet):
netcat localhost 10000
如果你的服務器正常工作,那么你就可以看到詩歌在你的屏幕上慢慢的打印出來。對!你會注意到每次服務器都會發(fā)送過一行的內(nèi)容過來。一旦詩歌傳送完畢,服務器就會關閉這條連接。
默認情況下,服務器只會監(jiān)聽本地回環(huán)的端口。如果你想連接另外一臺機子的服務器,你可以指定其IP地址內(nèi)容,命令行參數(shù)是 --iface選項。
不僅是服務器在發(fā)送詩歌的速度慢,而且讀代碼可以發(fā)現(xiàn),服務器在服務一個客戶端時其它連接進來的客戶端只能處于等待狀態(tài)而得不到服務。這的確是一個低效慢速的服務器,要不是為了學習,估計沒有任何其它用處。
在示例代碼中有一個可以從多個服務器中順序(一個接一個)地下載詩歌的阻塞模式的客戶端。下面讓這個客戶端執(zhí)行三個任務,正如第一個部分圖1描述的那樣。首先我們啟動三個服務器,提供三首不同的詩歌。在命令行中運行下面三條命令:
python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt --num-bytes 30
python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt
python blocking-server/slowpoetry.py --port 10002 poetry/science.txt
如果在你的系統(tǒng)中上面那些端口號有正在使用中,可以選擇其它沒有被使用的端口。注意,由于第一個服務器發(fā)送的詩歌是其它的三倍,這里我讓第一個服務器使用每次發(fā)送30個字節(jié)而不是默認的10個字節(jié),這樣一來就以3倍于其它服務器的速度發(fā)送詩歌,因此它們會在幾乎相同的時間內(nèi)完成工作。
現(xiàn)在我們使用阻塞模式的客戶端來獲取詩歌,運行如下所示的命令:
python blocking-client/get-poetry.py 10000 10001 10002
如果你修改了上面服務口器的端口,你需要在這里相應的修改以保持一致。由于這個客戶端采用的是阻塞模式,因此它會一首一首的下載,即只有在完成一首時才會開始下載另外一首。這個客戶端會像下面這樣打印出提示信息而不是將詩歌打印出來:
Task 1: get poetry from: 127.0.0.1:10000
Task 1: got 3003 bytes of poetry from 127.0.0.1:10000 in 0:00:10.126361
Task 2: get poetry from: 127.0.0.1:10001
Task 2: got 623 bytes of poetry from 127.0.0.1:10001 in 0:00:06.321777
Task 3: get poetry from: 127.0.0.1:10002
Task 3: got 653 bytes of poetry from 127.0.0.1:10002 in 0:00:06.617523
Got 3 poems in 0:00:23.065661
這圖1最典型的文字版了,每個任務下載一首詩歌。你運行后可能顯示的時間會與上面有所差別,并且也會隨著你改變服務器的發(fā)送時間參數(shù)而改變。嘗試著更改一下參數(shù)來觀測一下效果。
現(xiàn)在,我們來看看不用Twisted構建的異步模式的客戶端。首先,我們先運行它試試。啟動使用前面的三個端口來啟動三個服務器。如果前面開啟的還沒有關閉,那就繼續(xù)用它們好了。接下來,我們通過下面這段命令來啟動我們的異步模式的客戶端:
python async-client/get-poetry.py 10000 10001 10002
你或許會得到類似于下面的輸出:
Task 1: got 30 bytes of poetry from 127.0.0.1:10000
Task 2: got 10 bytes of poetry from 127.0.0.1:10001
Task 3: got 10 bytes of poetry from 127.0.0.1:10002
Task 1: got 30 bytes of poetry from 127.0.0.1:10000
Task 2: got 10 bytes of poetry from 127.0.0.1:10001
...
Task 1: 3003 bytes of poetry
Task 2: 623 bytes of poetry
Task 3: 653 bytes of poetry
Got 3 poems in 0:00:10.133169
這次的輸出可能會比較長,這是由于在異步模式的客戶端中,每次接收到一段服務器發(fā)送來的數(shù)據(jù)都要打印一次提示信息,而服務器是將詩歌分成若干片段發(fā)送出去的。值得注意的是,這些任務相互交錯執(zhí)行,正如第一部分圖3所示。
嘗試著修改服務器的設置(如將一個服務器的延時設置的長一點),來觀察一下異步模式的客戶端是如何針對變慢的服務器自動調節(jié)自身的下載來與較快的服務器保持一致。這正是異步模式在起作用。
還需要值得注意的是,根據(jù)上面的設置,異步模式的客戶端僅在10秒內(nèi)完成工作,而同步模式的客戶端卻使用了23秒?,F(xiàn)在回憶一下第一部分中圖3與圖4.通過減少阻塞時間,我們的異步模式的客戶端可以在更短的時間里完成下載。誠然,我們的異步客戶端也有些阻塞發(fā)生,那是由于服務器太慢了。由于異步模式的客戶端可以在不同的服務器來回切換,它比同步模式的客戶產(chǎn)生的阻塞就少得多。
現(xiàn)在讓我們來讀一下異步模式客戶端的代碼。注意其與同步模式客戶端的差別:
異步模式中客戶端的核心就是最高層的循環(huán)體,即get_poetry函數(shù)。這個函數(shù)可以被拆分成兩個步驟:
可以看出,同步模式客戶端也有個循環(huán)體(在main函數(shù)內(nèi)),但是這個循環(huán)體的每個迭代都是完成一首詩的下載工作。而在異步模式客戶端的每次迭代過程中,我們可以完成所有詩歌的下載或者是它們中的一些。我們并不知道在一個迭代過程中,在下載哪首詩,或者一次迭代中我們下載了多少數(shù)據(jù)。這些都依賴于服務器的發(fā)送速度與網(wǎng)絡環(huán)境。我們只需要select函數(shù)告訴我們哪個socket有數(shù)據(jù)需要接收,然后在保證不阻塞程序的前提下從其讀取盡量多的數(shù)據(jù)。
如果在服務器端口固定的條件下,同步模式的客戶端并不需要循環(huán)體,只需要順序羅列三個get_poetry就可以了。但是我們的異步模式的客戶端必須要有一個循環(huán)體來保證我們能夠同時監(jiān)視所有的socket端。這樣我們就能在一次循環(huán)體中處理盡可能多的數(shù)據(jù)。
這個利用循環(huán)體來等待事件發(fā)生,然后處理發(fā)生的事件的模型非常常見,而被設計成為一個模式:reactor模式。其圖形化表示如圖5所示:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p02_reactor-1.png" alt="" />
這個循環(huán)就是個"reactor"(反應堆),因為它等待事件的發(fā)生然后對其作相應的反應。正因為如此,它也被稱作事件循環(huán)。由于交互式系統(tǒng)都要進行I/O操作,因此這種循環(huán)也有時被稱作select loop,這是由于select調用被用來等待I/O操作。因此,在本程序中的select循環(huán)中,一個事件的發(fā)生意味著一個socket端處有數(shù)據(jù)來到。值得注意的是,select并不是唯一的等待I/O操作的函數(shù),它僅僅是一個比較古老的函數(shù)而已(因此才被用的如此廣泛)?,F(xiàn)在有一些新API可以完成select的工作而且性能更優(yōu),它們已經(jīng)在不同的系統(tǒng)上實現(xiàn)了。不考慮性能上的因素,它們都完成同樣的工作:監(jiān)視一系列sockets(文件描述符)并阻塞程序,直到至少有一個準備好的I/O操作。
嚴格意義上來說,我們的異步模式客戶端中的循環(huán)并不是reactor模式,因為這個循環(huán)體并沒有獨立于業(yè)務處理(在此是接收具體的服務器傳送來的詩歌)之外。它們被混合在一起。一個真正reactor模式的實現(xiàn)是需要實現(xiàn)循環(huán)獨立抽象出來并具有如下的功能:
一個設計優(yōu)秀的reactor模式實現(xiàn)需要做到:
好了,我們上面所說的其實就是Twisted — 健壯、跨平臺實現(xiàn)了reactor模式并含有很多附加功能。
在第三部分中,實現(xiàn)Twisted版的下載詩歌服務時,我們將開始寫一些簡單的Twisted程序。
本部分原作參見: dave http://krondo.com/?p=1247
本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100pyhh.html