第一個twisted支持的詩歌服務器html
儘管Twisted大多數狀況下用來寫服務器代碼,爲了一開始儘可能從簡單處着手,咱們首先從簡單的客戶端講起。python
讓咱們來試試使用Twisted的客戶端。源碼在twisted-client-1/get-poetry.py。首先像前面同樣要開啓三個服務器:react
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
而且運行客戶端:git
python twisted-client-1/get-poetry.py 10000 10001 10002
你會看到在客戶端的命令行打印出:程序員
Task 1: got 60 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 3: got 10 bytes of poetry from 127.0.0.1:10002 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.134220
和咱們的沒有使用Twisted的非阻塞模式客戶端打印的內容接近。這並不奇怪,由於它們的工做方式是同樣的。github
下面,咱們來仔細研究一下它的源代碼。編程
注意:正如我在第一部分說到,咱們開始學習使用Twisted時會使用一些低層Twisted的APIs。這樣作是爲揭去Twisted的抽象層,這樣咱們就能夠從內向外的來學習Tiwsted。可是這就意味着,咱們在學習中所使用的APIs在實際應用中可能都不會見到。記住這麼一點就行:前面這些代碼只是用做練習,而不是寫真實軟件的例子。緩存
可民看到,首先建立了一組PoetrySocke
t
的實例。在PoetrySocket
初始化時
,其建立了一個網絡
socket
做爲本身的屬性字段來鏈接服務器,而且選擇了非阻塞模式:
服務器
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect(address) self.sock.setblocking(0)
最終咱們雖然
會提升到不使用
socket
的抽象層次上,但這裏咱們仍然須要使用它。在建立完
socket
後,
PoetrySocket
經過方法
addReader
將本身傳遞給
reactor
:
網絡
# tell the Twisted reactor to monitor this socket for reading from twisted.internet import reactor reactor.addReader(self)
這個方法
給
Twisted
提供了一個文件描述符來監視要發送來的數據。爲何咱們不傳遞給
Twisted
一個文件描述符或回調函數而是一個對象實例?而且
Twisted
內部沒有任何與這個詩歌服務相關的代碼,它怎麼知道該如何與咱們的對象實例交互?相信我,我已經查看過了,打開
twisted.internet.interfaces
模塊,和我一塊兒來搞清楚是怎麼回事。
Twisted
接口
在
twisted
內部有不少被稱做
接口
的子模塊。每一個都定義了一組接口類。因爲在
8.0
版本中,
Twisted
使用
zope.interface
做爲這些類的基類。但咱們這裏並不來討論它其中的細節。咱們只關心其在
Twisted
的子類,就是你看到的那些。
使用接口
的核心目的之一就是文檔化。做爲一個
python
程序員,你確定知道
Duck Typing
。(說實話我還真不懂這種編程,但經過查看資料,其實就是動態編程的思想,根據你的動做來肯定你的類型)
翻閱
twisted.internet.interfaces
找到方法的
addReader
定義,它的定義在
IReactorFDSet
中能夠找到:
def addReader(reader): """ I add reader to the set of file descriptors to get read events for. @param reader: An L{IReadDescriptor} provider that will be checked for read events until it is removed from the reactor with L{removeReader}. @return: C{None}. """
IReactorFDSet
是一個
Twisted
的
reactor
實現的接口。所以任何一個
Twisted
的
reactor
都會一個
addReader
的方法,如同上面描述的同樣工做。這個方法聲明之因此沒有
self
參數是由於它僅僅關心一個公共接口定義,
self
參數僅僅是接口實現時的一部分(在調用它時,也沒有顯
式地
傳入一個
self
參數)。接口類永遠不會被實例化或做爲基類來繼承實現。
注意
1
:技術上講,
IReactorFDSet
只會由
reactor
實現用來監聽文件描述符。具我所知,如今全部已實現
reactor
都會實現這個接口。
注意
2
:使用接口並不只僅是爲了文檔化。
zope.interface
容許你顯式地來聲明一個類實現一個或多個接口,並提供運行時檢查這些實現的機制。一樣也提供代理這一機制,它能夠動態地爲一個沒有實現某接口的類直接提供該接口。但咱們這裏就不作深刻學習了。
注意
3
:你可能已經注意到接口與最近添加到
Python
中虛基類的類似性了。這裏咱們並不去分析它們之間的類似性與差別。若你有興趣,能夠讀讀
Ptyhon
項目的創始人
Glyph
寫的一篇關於這個話題的文章。
根據文檔的描述能夠看出,
addReader
的
reader
參數是要實現
IreadDescriptor
接口的。這也就意味咱們的
PoetrySocket
也必須這樣作。
閱讀接口模塊咱們能夠看到下面這段代碼:
class IReadDescriptor(IFileDescriptor): def doRead(): """ Some data is available for reading on your descriptor. """
同時
你會看到在咱們的
PoetrySocket
類中有一個
doRead
方法。
當其被
Twisted
的
reactor
調用時,就會採用異步的方式從
socket
中讀取數據。所以,
doRead
其實就是一個回調函數,只是沒有直接將其傳遞給
reactor
,而是傳遞一個實現此方法的對象實例。這也是
Twisted
框架中的慣例—不是直接傳遞實現某個接口的函數而是傳遞實現它的對象。這樣咱們經過一個參數就能夠傳遞一組相關的回調函數。並且也可讓回調函數之間經過存儲在對象中的數據進行通訊。
那在
PoetrySocket
中實現其它的回調函數呢?注意到
IReadDescriptor
是
IFileDescriptor
的一個子類。這也就意味任何一個實現
IReadDescriptor
都必須實現
IFileDescriptor
。如果你仔細閱讀代碼會看到下面的內容:
class IFileDescriptor(ILoggingContext): """ A file descriptor. """ def fileno(): ... def connectionLost(reason): …
我
將文檔描述
省略掉
了,但這些函數的功能從字面上就能夠理解:
fileno
返回咱們想監聽的文件描述符,
connectionLost
是當鏈接關閉時被調用。你也看到了,
PoetrySocket
實現了這些方法。
最後,
IFileDescriptor
繼承了
ILooggingContext
,這裏我不想再展示其源碼。我想說的是,這就是爲何咱們要實現一個
logPrefix
回調函數。你能夠在
interface
模塊中找到答案。
注意
:你也許注意到了,當鏈接關閉時,在
doRead
中返回了一個特殊的值。我是如何知道的?說實話,沒有它程序是沒法正常工做的。我是在分析
Twisted
源碼中發現其它相應的方法採起相同的方法。你也許想好好研究一下:但有時一些文檔或書的解釋是錯誤的或不完整的。所以可能當你搞清楚怎麼回事時,咱們已經完成第五部分了呵呵。
更多關於回調的知識
咱們使用
Twisted
的異步客戶端和前面的沒有使用
Twisted
的異步客戶很是的類似。二者都要鏈接它們本身的
socket
,並以異步的方式從中讀取數據。最大的區別在於:使用
Twisted
的客戶端並無使用本身的
select
循環
-
而使用了
Twisted
的
reactor
。
doRead
回調函數是很是重要的一個回調。
Twisted
調用它來告訴咱們已經有數據在
socket
接收完畢。我能夠經過圖
7
來形象地說明這一過程:
圖
7 doRead
回調過程
每當回調
被激活,就輪到咱們的代碼將全部可以讀的數據讀回來而後非阻塞式的中止。正如咱們第三部分說的那樣,
Twisted
是不會由於什麼異常情況(如沒有必要的阻塞)而終止咱們的代碼。那麼咱們就故意寫個會產生異常情況的客戶端看看到底能發生什麼事情。能夠在
twisted-client-1/get-poetry-broken.py中看到源代碼。這個客戶端與你前面看到的一樣有兩個異常情況出現:
1.
這個客戶端並不沒有選擇非阻塞式的
socket
2.doRead
回調方法在
socket
關閉鏈接前一直在不停地讀
socket
如今讓咱們運行一下這個客戶端:
python twisted-client-1/get-poetry-broken.py 10000 10001 10002
咱們出獲得如同下面同樣的輸出:
Task 1: got 3003 bytes of poetry from 127.0.0.1:10000 Task 3: got 653 bytes of poetry from 127.0.0.1:10002 Task 2: got 623 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.132753
可能
除了任務的完成順序不太一致外,和我先阻塞式客戶端是同樣的。這是由於這個客戶端是一個阻塞式的。
因爲使用
了阻塞式的鏈接,就將咱們的非阻塞式客戶端變成了阻塞式的客戶端。這樣一來,咱們儘管遭受了使用
select
的複雜但卻沒有享受到其帶來的異步優點。
像諸如
Twisted
這樣的事件循環所提供的多任務的能力是須要用戶的合做來實現的。
Twisted
會告訴咱們何時讀或寫一個文件描述符,但咱們必需要儘量高效而沒有阻塞地完成讀寫工做。一樣咱們應該禁止使用其它各種的阻塞函數,如
os.system
中的函數。除此以外,當咱們遇到計算型的任務(長時間佔用
CPU
),最好是將任務切成若干個部分執行以讓
I/O
操做盡量地執行。
你也許已經注意到這個客戶端所花費的時間少於先前那個阻塞的客戶端。這是因爲這個在一開始就與全部的服務創建鏈接,因爲服務是一旦鏈接創建就當即發送數據,並且咱們的操做系統會緩存一部分發送過來但尚讀不到的數據到緩衝區中(緩衝區大小是有上限的)。所以就明白了爲何前面那個會慢了:它是在完成一個後再創建下一個鏈接並接收數據。
但這種小優點僅僅在小數據量的狀況下才會得以體現。若是咱們下載三首
20M
個單詞的詩,那時
OS
的緩衝區會在瞬間填滿,這樣一來咱們這個客戶端與前面那個阻塞式客戶端相比就沒有什麼優點可言了。
結束語
我沒有過多地解釋此部分第一個客戶端的內容。你可能注意到了,
connectionLost
函數會在沒有
PoetrySocket
等待詩歌后關閉
reactor
。因爲咱們的程序除了下載詩歌不提供其它服務,因此纔會這樣作。但它揭示了兩個低層
reactor
的
APIs
:
removeReader
和
getReaders
。
還有與咱們客戶端使用的
Readers
的
APIs
類同的
Writers
的
APIs
,它們採用相同的方式來監視咱們要發送數據的文件描述符。能夠經過閱讀
interfaces
文件來獲取更多的細節。讀和寫有各自的
APIs
是由於
select
函數須要分開這兩種事件(讀或寫能夠進行的文件描述符)。固然了,能夠等待即能讀也能寫的文件描述符。
第五部分,咱們將使用
Twisted
的高層抽象方式實現另一個客戶端,而且學習更多的
Twisted
的接口與
APIs
。