python twisted教程 二:缓慢的诗
我的假設
我假設你已經有一些基本的能力去寫python程序,并且直到一些socket編程方面的額知識,如果你沒有接觸過socket請移步這里 socket module documention, 這個系列中的代碼例子都是運行在python2.5 和twisted 8.2.0,程序如果不能正確運行請檢查你的python和twisted 的版本.
獲取例子代碼
你可以在public git repository獲取例子代碼,如果你可以用git 或者其他的版本管理軟件,你可以直接clone 出一份代碼:
git clone git://github.com/jdavisp3/twisted-intro.git
緩慢的詩
盡管cpu比網絡要快很多,但是網絡還是仍舊會比你的腦袋或者眼睛快.所以你要看到cpu-network是怎樣運行幾乎是不可能的.我們需要的是一個slow server,可以讓我們的眼睛看到變化.既然是一個server,那就讓我們的server來生產兩首頗具詩意的詩吧,在代碼倉庫的子目錄中有John Donne, W.B. Yeats, 和 Edgar Allen Poe 的三首小詩,當然如果換成你自己寫的,that will be nice :)
最基本的”poetry server” 的實現在 blocking-server/slowpoetry.py,你可以讓他跑起來:
python blocking-server/slowpoetry.py poetry/ecstasy.txt
上面這個命令會開啟一個阻塞的服務器,你可以打開這個阻塞服務器的源碼看一下,你可以看到我們并沒有用到twisted,只有一些簡單的socket 操作.它可以在每個固定的延遲后發出固定的數量的字節,默認的它會每0.1s 發出10個字節.你可以通過–num-bytes 和 –delay 來控制時間和字節數,比如下面:
python blocking-server/slowpoetry.py --num-bytes 50 --delay 5 poetry/ecstasy.txt
當這個服務開始運行的時候,它會打印出它監聽的端口.默認的,這個是一個隨機的端口,你可以通過修改配置讓它監聽固定的端口,你可以指定它監聽的端口如下:
python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt
如果你有netcat 命令的話,你可以測試這個server 通過一下的命令:
netcat localhost 10000
如果一切正常的話,你會看到一條小詩被每次輸出一些字節,一旦這首小詩被完全生產出來,服務器會斷開連接.
如果你看源碼的話你會發現,poetry server 不僅僅是每次輸出一些字節,如果你再用其他的client去連接server 的話,其他的client 必須等到第一個client被處理完才會被poetry server 處理. 現在的poetry server 確實很慢啊,慢的我蛋痛.
阻塞的client
在我們的例子中我們的客戶端也是阻塞的,它可以從多個server 上一個接一個的讀取內容,現在我們給我們的客戶端三個任務去執行,就想圖片一中的那樣.首先我們先啟動三個服務器,為三個不同的客戶端來服務,如下:
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
接下來我們可以開啟阻塞的客戶端blocking-client/get-poetry.py來接收小詩了:
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
基本上就是圖片一的文字版本.你可以看一下源代碼去定位一下在接收和發送字節的時候那些地方會產生阻塞.
異步的client
下面讓我們看看一個簡單的異步的client,沒有用twisted.先讓我們運行一下, async-client/get-poetry.py:
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
這一次的輸出結果會比較長因為每當異步的client 每一次從任何一個服務器上獲取一些東西都會輸出.注意,這個異步的client 會交錯的執行三個任務,就像在圖片三中描述的那樣.
需要注意的是,異步客戶端完成任務用了差不多10s,而同步的客戶端需要差不多23s,現在回想一下圖片三和圖片四的區別.通過減少阻塞的時間,我們的異步客戶端需要更少的時間.我們的異步的客戶端也是會有一些阻塞,但通過交錯任務來執行可以減少很多阻塞的時間.
嚴格的來說,我們的異步client也在執行阻塞操作:它會向stdout輸出一些內容.但對我們的例子還沒有太大的影響.終端其實一直在準備接收更多的print 的輸出,所以print 并不會阻塞,和我們的slow 服務器來說,print語句很快.如果我們的異步程序是一個進程中管道的一部分,在處理標準的輸入和輸出的時候應該考慮用異步的I/O.twisted 已經提供了在處理標準輸入輸出時異步I/O 的支持,為了kiss 原則,這里先不用
更進一步的
讓我們看一下異步client 的代碼,注意異步client代碼和同步client代碼的區別:
????異步的client會一次連接多個server,而同步的client
????異步的client中的socket 通過setblocking(0)設置為了非阻塞的
????select 模塊的selsect可以用來監聽是否有socket可以提供給client 的數據
????從服務器讀數據的時候,異步client會盡可能的多讀數據直到這個socket被阻塞,然后如果有其他的socket是可讀的話就轉到下一個,這意味著我們必須要記錄每一首小詩傳輸狀態
這個異步client 中最核心的部分是get_poetry 函數的上層的事件循環,這個循環要經過一下的過程:
????用select監聽所有打開的sockets,直到其中的一個socket有數據流可以讀
????如果有一個socket有數據可讀,讀取它
????重復循環,直到所有的socket都關閉
同步的client在main 函數中也有一個循環,但是每次遍歷的時候同步客戶端只下載同一首小詩,直到這首小詩讀完.而異步的client在每次循環的時候會讀取多個小詩的片段,我們不確定在一次循環中他會讀取幾個小詩,或者每次讀取多少字節, 這完全依賴于服務器的速度以及網絡的速度.我們讓select 告訴我們哪一個socket有數據可以讀,然后我們就可以在不產生阻塞的情況下盡可能多的去讀數據了.
假如一個同步的client一直連在一個相同的服務器上(讓我們假設是三號服務器),它現在根本不需要一個外部的循環,因為get_poety函數是阻塞的,客戶端會一直連在同一個server上,直到獲取完整個的一首詩.而異步的客戶端沒有一個事件循環的話則無法進行下去,異步的客戶端需要在一開始的就監聽全部的socket,然后在每一次的循環中處理所能收受的所有數據.
這種用一個循環去等待事件的發生,然后去處理事件的模式我們很常見,已經形成了一種設計模式叫做:reactor pattern(反應堆模式),下面這張圖比較直觀的描述了反應堆模式
the reactor loop
這個循環就是一個反應器因為他在不斷的等待事件的發生然后響應他們,因為這個原因也被叫做事件循環.因為”反應器”系統總是等待I/O,這些循環有時也被叫做 select loops,select call 也經常等待I/O. 在一個select loop 中,一個事件就是其中一個socket 變為可讀的或者可寫的.記住 select 不是等待I/O 的唯一的方法,還有其他的方法被用做等待I/O(比如epoll),他們甚至比select 的性能要好,拋開性能不說,他們都用來做同一件事情:監聽一些端口,然后等待其中的一些端口可以讀或者可以寫.
用select來監測文件描述符來實現一個簡單的非阻塞程序是可能的,在" 反應器系統中"執行一些不涉及I/O的操作也是可以的.但是在一個真正的反應器系統中,所有的工作都是I/O相關的
嚴格的來,咱們的異步client 不是” 反應堆模式”,因為循環的邏輯和也業務邏輯沒有完全分開,他們交錯在一起.一個真正的反應器模式應該讓事件循環作為一個抽象來實現以下兩點:
????1,接收一些你要處理I/O的文件描述符
????2,當任何一個文件可以讀或者可以寫的時候,獨立的告訴你
一個非常好的反應堆模式還應實現:
????可以處理各種操作系統各種奇怪的實現
????提供一個非常好的抽象的實現去幫助你很容易的去應用reactor
????提供各種公開的協議的實現
好吧,其實我們想說的就是twisted,一個具有魯棒性很強,跨平臺的,包羅萬象的反應器模式的實現.第三部分我們將開始寫一些簡單的twisted 代碼了.ARE YOU READY?
?
?
?
總結
以上是生活随笔為你收集整理的python twisted教程 二:缓慢的诗的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: web靶机:kali linux 2.0
- 下一篇: twisted系列教程八–延迟的诗