第二部分:低效的詩歌服務器來啓發對Twisted機制的理解html
這個系列是從這裏開始的,歡迎你再次來到這裏來。如今咱們可能要寫一些代碼。在開始以前,咱們都作出一些必要的假設。python
關於對你的假設react
在展開討論前,我假設你已經有過用Python寫同步程序的經歷而且至少知道一點有關Python的Sockt編程的經驗。若是你從沒有寫過Socket程序,或許你能夠去看看Socket模塊的文檔,尤爲是後面的示例代碼。若是你沒有用過Python的話,那後面的描述對你來講可能比看周易還痛苦。git
你所使用的計算機的狀況(想的真周到,:))github
我通常是在Linux上使用Twisted,這個系列的示例代碼也是在Linux下完成的。首先聲明的是我並無故意讓代碼失去平臺無關性,但我所講述的一些內容確實可能僅僅適應於Linux和其它的類Unix(好比MAC OSX或FreeBSD)。WIndows是個奇怪詭異的地方(??爲何這麼評價Windows呢),若是你想嘗試在它上面學習這個系列,抱歉,若是出了問題,我沒法提供任何幫助。shell
而且假設你已經安裝了近期版本的Python和Twisted。我所提供的示例示例代碼是基於Python2.5和Twisted8.2.0。編程
你能夠在單機上運行全部的示例代碼,也能夠在網絡系統上運行它們。可是爲了學習異步編程的機制,單機上學習是比較理想的。服務器
獲取代碼的方法網絡
使用git工具來獲取Dave的最新示例代碼。在shell或其它命令行上輸入如下命令(假設已經安裝git):異步
git clone git://github.com/jdavisp3/twisted-intro.git
下載結束後,解壓並進入第一層文件夾(你能夠看到有個README文件)。
低效的詩歌服務器
雖然CPU的處理速度遠遠快於網絡,但網絡的處理速度仍然比人腦快,至少比人類的眼睛快。所以,想經過網絡來得到CPU的視角是很困難的,尤爲是在單機的迴環模式中數據流全速傳輸時,更是困難重重。
咱們所須要的是一個慢速低效詩歌服務器,其用人爲的可變延時來體現影響結果。畢竟服務器要提供點東西嗎,咱們就提供詩歌好了。目錄下面有個子目錄專門存放詩歌用的。
最簡單的慢速詩歌服務器在blocking-server/slowpoetry.py中實現。你可用下面的方式來運行它。
python blocking-server/slowpoetry.py poetry/ecstasy.txt
上面這個命令將啓動一個阻塞的服務器,其提供「Ecstasy」這首詩。如今咱們來看看它的源碼內容,正如你所見,這裏面並無使用任何Twisted的內容,只是最基本的Socket編程操做。它每次只發送必定字節數量的內容,而每次中間延時一段時間。默認的是每隔0.1秒發送10個比特,你能夠經過 -delay 和 -num-bytes參數來設置。例如每隔5秒發送50比特: python blocking-server/slowpoetry.py --num-bytes 50 –delay 5poetry/ecstasy.txt
當服務器啓動時,它會顯示其所監聽的端口號。默認狀況下,端口號是在可用端口號池中隨機選擇的。你可能想使用固定的端口號,那麼無需更改代碼,只須要在啓動命令中做下修改就OK了,以下所示:
python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt
若是你裝有netcat工具,能夠用以下命令來測試你的服務器(也能夠用telnet):
netcat localhost 10000
若是你的服務器正常工做,那麼你就能夠看到詩歌在你的屏幕上慢慢的打印出來。對!你會注意到每次服務器都會發送過一行的內容過來。一旦詩歌傳送完畢,服務器就會關閉這條鏈接。
默認狀況下,服務器只會監聽本地迴環的端口。若是你想鏈接另一臺機子的服務器,你能夠指定其IP地址內容,命令行參數是 -iface選項。
不只是服務器在發送詩歌的速度慢,並且讀代碼能夠發現,服務器在服務一個客戶端時其它鏈接進來的客戶端只能處於等待狀態而得不到服務。這的確是一個低效慢速的服務器,要不是爲了學習,估計沒有任何其它用處。
阻塞模式的客戶端
在示例代碼中有一個能夠從多個服務器中順序(一個接一個)地下載詩歌的阻塞模式的客戶端。下面讓這個客戶端執行三個任務,正如第一個部分圖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
若是在你的系統中上面那些端口號有正在使用中,能夠選擇其它沒有被使用的端口。注意,因爲第一個服務器發送的詩歌是其它的三倍,這裏我讓第一個服務器使用每次發送30個字節而不是默認的10個字節,這樣一來就以3倍於其它服務器的速度發送詩歌,所以它們會在幾乎相同的時間內完成工做。
如今咱們使用阻塞模式的客戶端來獲取詩歌,運行以下所示的命令:
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最典型的文字版了,每一個任務下載一首詩歌。你運行後可能顯示的時間會與上面有所差異,而且也會隨着你改變服務器的發送時間參數而改變。嘗試着更改一下參數來觀測一下效果。
異步模式的客戶端
如今,咱們來看看不用Twisted構建的異步模式的客戶端。首先,咱們先運行它試試。啓動使用前面的三個端口來啓動三個服務器。若是前面開啓的尚未關閉,那就繼續用它們好了。接下來,咱們經過下面這段命令來啓動咱們的異步模式的客戶端:
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
此次的輸出可能會比較長,這是因爲在異步模式的客戶端中,每次接收到一段服務器發送來的數據都要打印一次提示信息,而服務器是將詩歌分紅若干片斷髮送出去的。值得注意的是,這些任務相互交錯執行,正如第一部分圖3所示。
嘗試着修改服務器的設置(如將一個服務器的延時設置的長一點),來觀察一下異步模式的客戶端是如何針對變慢的服務器自動調節自身的下載來與較快的服務器保持一致。這正是異步模式在起做用。
還須要值得注意的是,根據上面的設置,異步模式的客戶端僅在10秒內完成工做,而同步模式的客戶端卻使用了23秒。如今回憶一下第一部分中圖3與圖4.經過減小阻塞時間,咱們的異步模式的客戶端能夠在更短的時間裏完成下載。誠然,咱們的異步客戶端也有些阻塞發生,那是因爲服務器太慢了。因爲異步模式的客戶端能夠在不一樣的服務器來回切換,它比同步模式的客戶產生的阻塞就少得多。
更近一步的觀察
如今讓咱們來讀一下異步模式客戶端的代碼。注意其與同步模式客戶端的差異:
1.異步模式客戶端一次性與所有服務器完成鏈接,而不像同步模式那樣一次只鏈接一個。
2.用來進行通訊的Socket方法是非阻塞模的,這是經過調用setblocking(0
來實現的。
3.select模塊中的select方法是用來識別是其監視的socket是否有完成數據接收的,若是沒有
它就處於阻塞狀態。
4.當從服務器中讀取數據時,會盡可能多地從Sockt讀取數據直到它阻塞爲止,而後讀下一個Sockt
接收的數據(若是有數據接收的話)。這意味着咱們須要跟蹤記錄從不一樣服務器傳送過來詩歌的接
收狀況(由於,一首詩的接收並非連續完成,因此須要保證每一個任務的可連續性,就得有冗餘
的信息來完成這一工做)。
異步模式中客戶端的核心就是最高層的循環體,即get_poetry函數。這個函數能夠被拆分紅兩個步驟:
1.使用select函數等待全部Socket,直到至少有一個socket有數據到來。
2.對每一個有數據須要讀取的socket,從中讀取數據。但僅僅只是讀取有效數據,不能爲了等待還沒
來到的數據而發生阻塞。
3.重複前兩步,直到全部的socket被關閉。
能夠看出,同步模式客戶端也有個循環體(在main函數內),可是這個循環體的每一個迭代都是完成一首詩的下載工做。而在異步模式客戶端的每次迭代過程當中,咱們能夠完成全部詩歌的下載或者是它們中的一些。咱們並不知道在一個迭代過程當中,在下載那首詩,或者一次迭代中咱們下載了多少數據。這些都依賴於服務器的發送速度與網絡環境。咱們只須要select函數告訴咱們那個socket有數據須要接收,而後在保證不阻塞程序的前提下從其讀取儘可能多的數據。
若是在服務器端口固定的條件下,同步模式的客戶端並不須要循環體,只須要順序羅列三個get_poetry
就能夠了。可是咱們的異步模式的客戶端必需要有一個循環體來保證咱們可以同時監視全部的socket端。這樣咱們就能在一次循環體中處理儘量多的數據。
這個利用循環體來等待事件發生,而後處理髮生的事件的模型很是常見,而被設計成爲一個模式:reactor模式。其圖形化表示如圖5所示:
這個循環就是個」reactor「(反應堆),由於它等待事件的發生然對其做爲相應的反應。正由於如此,它也被稱做事件循環。因爲交互式系統都要進行I/O操做,所以這種循環也有時被稱做select loop,這是因爲select調用被用來等待I/O操做。所以,在本程序中的select循環中,一個事件的發生意味着一個socket端處有數據來到。值得注意的是,select並非惟一的等待I/O操做的函數,它僅僅是一個比較古老的函數而已(所以才被用的如此普遍)。如今有一些新API能夠完成select的工做並且性能更優,它們已經在不一樣的系統上實現了。不考慮性能上的因素,它們都完成一樣的工做:監視一系列sockets(文件描述符)並阻塞程序,直到至少有一個準備好時行I/O操做。
嚴格意義上來講,咱們的異步模式客戶端中的循環並非reactor模式,由於這個循環體並無獨立於業務處理(在此是接收具體個服務器傳送來的詩歌)以外。它們被混合在一塊兒。一個真正reactor模式的實現是須要實現循環獨立抽象出來並具備以下的功能:
1.監視一系列與你I/O操做相關的文件描述符(description)
2.不停地向你彙報那些準備好I/O操做的文件描述符
一個設計優秀的reactor模式實現須要作到:
1.處理全部不一樣系統會出現的I/O事件
2.提供優雅的抽象來幫助你在使用reactor時少花些心思去考慮它的存在
3.提供你能夠在抽象層外(treactor實現)使用的公共協議實現。
好了,咱們上面所說的其實就是Twisted—健壯、跨平臺實現了reactor模式並含有不少附加功能。
在第三部分中,實現Twisted版的下載詩歌服務時,咱們將開始寫一些簡單的Twisted程序。