python twised系列教程四–twisted Poetry client
我們第一個twisted client
盡管twisted 經常被用來寫server端的,但client往往會比較簡單些,我們就以最簡單的client 開始.源代碼在twisted-client-1/get-poetry.py,首先開啟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
然后運行client:
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
就像我們先前寫的異步的client一樣,他們實質上做了相同的事情.讓我們看一下源碼看一下它是怎樣工作的,把代碼在你的編輯器中打開,以至于你知道我們在討論什么.
就像在第一部分說的,我們剛開始接觸twisted會盡量的用他的底層的api,這樣我們更能了解twisted底層的東西.]
也就是說我們一開始用的一些底層api 在往后的項目中可能不會用到.我們只是學習練習twisted的用法.
這個twisted client 建立了幾個PoetrySocket 對象.一個PoetrySocket 創造一個socket,連接一個server,然后設置非阻塞模式:
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 的是PoetySocket 對象而不是文件描述符和callback? twisted 是怎樣知道怎樣操作PoetySocket 的?相信我吧,我已經觀察過了. 打開twisted.internet.interfaces模塊,并跟隨我.
Twisted Interfaces
在twisted 中有很多叫做接口的子模塊.每一個模塊定義了一些 接口類.在twisted 8.0 版本中, zope.interface是這些類的基礎,但是這個包對我們不是特別重要,我們關心的是這些接口的子類,就像我們將要看到的這個.
作為一個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 reactors 實現的眾多接口中的一個.因此任何的twisted reactor 對象都有一個addReader 的方法. 這個方法的聲明并沒有一個self 參數,因為它只是被定義為一個接口.接口對象永遠不會被實例化或用作基類.
????技術上講,IReactorFDSet僅僅被reactor實現,被用來等待文件描述符,
????據我所知,IReactorFDSet 已經包含了reactor 的所有實現
????zope.interface 允許你明確的聲明一個類實現了一個或多個接口,并提供相應的機制在運行時來檢查這些聲明. 也支持適配器,可以動態的為一個對象提供一個它本身不支持的接口.感興趣的同學移步這里
????你可能注意到了接口和抽象的基類的相同之處,都是對python 語言的擴展 ,我們在這里不講他們之間的相同點和不同點.如果你感興趣的話你可以讀讀 essay
?
根據上面講的,addReader 方法的參數reader 應該實現IReadDescriptor接口.這就意味著我們的PoetrySocket 對象要實現這個接口. 我們可以找到IReadDescriptor 接口的定義:
class IReadDescriptor(IFileDescriptor):
????def doRead():
????????"""
????????Some data is available for reading on your descriptor.
????????"""
你會發現在我們的PoetrySocket 中doRead 方法的實現.當它被twised 的reactor調用時,它會從socket 中異步地讀取數據.可見doRead 確實是一個callback,我們并沒有把它直接傳遞給reactor,我們把它包裝進PoetrySocket.這在twisted framework 中是個習慣–不是傳遞一個函數而是傳遞一個實現了某些接口的對象. 這樣就允許我們一次可以傳遞多個callback,而且可以讓多個callback可以通過一個對象中的共享數據互相通信.
PoetrySocket 對象的其他方法實現了什么?注意IReadDescriptor是IFileDescriptor子類,這就意味著實現了IReadDescriptor 的對象也必須實現IFileDescriptor,如果你看一下IFileDescriptor,你會發現:
class IFileDescriptor(ILoggingContext):
????"""
????A file descriptor.
????"""
????def fileno():
????????...
????def connectionLost(reason):
????????...
我省略了一部分注釋,但是你可以從方法名中看到它們的用途:fileno 會返回我們想監聽的文件描述符,connectionLost會在連接丟失的時候被訪問.你會發現我們的PoetrySocket 也實現了這兩個方法.
最后,IFileDescriptor繼承至ILoggingContext,所以我們也要實現logPrefix callback,你可以在interfaces 模塊中獲取更詳細的信息.
注意:你可能注意到doRead 會返回一個特殊值來表明什么時候這個socket被關閉.我是怎樣知道這樣做的呢?
一般來說,如果不這樣做就不會工作,我查看了其他的實現了相同接口的方法.你不得不記住:有時候一些軟件
的文檔是不正確的.(略)
More on Callbacks
我們的新的twisetd client 和我們原先寫的異步的client 確實很相像.兩個client全都建立它們自己的socket,然后從這些socket 中異步的讀數據.不同的是twsited cliet 不用自己實現select loop– 它用 twisted 的reactor 來實現.
doRead callback 是最重要的一個,twisted 通過它告訴我們socket 中已經有數據準備好了,我們可以把它形象化成圖七
圖片七
每一次callback被觸發的時候,我們就盡可能的讀數據然后停止(非阻塞的),就像我們在第三部分講的,twisted 不能防止我們的代碼是阻塞的.我們可以那樣做一下然后看會發生什么. 在跟我們的twisted client 相同目錄下有一個twisted-client-1/get-poetry-broken.py,這個client 跟咱們的twisted client 有兩點不一樣:
????這個client 沒有設置為非阻塞
????doRead方法不停的讀數據直到一個socket被關閉
下面運行這個client:
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
輸出跟我們以前的twisted client 不太一樣,這是因為這個broken-client 為阻塞的.在callback中通過使用一個阻塞的操作,把我們以前的異步twisted程序編程了同步的twisted程序.我們得到了一個復雜的select 循環,并沒有從異步中得到什么好處.
twisted 提供給我的多任務的處理方式是合作,twisted 會告訴什么時候去讀什么時候去寫,但是我們必須在盡可能處理多的數據情況下又要防止阻塞.此外我們必須避免其他的阻塞的操作,比如os.system.假如我們有一個很耗費cpu資源的任務,這就要求我們把它拆成小的交替的任務,以便i/o操作不會造成阻塞.
注意我們的broken- client仍舊會工作,但就是不能利用異步I/O 的效率,你可能仍舊注意到我們的broken-client 仍會比單純的同步阻塞client 要快,那是因為broken -client 在一開始的時候就連接到所有的server,server端會立刻送出數據,操作系統會緩沖這些數據,即使我們沒有讀取它們,broken-client 會有效的獲取到其他server傳來的數據,即使broken-client在一段時間內只有一個在讀.
這種buffer 的把戲只會在只有小量數據的時候管用,如果數據量很大的的話broken-client 會和我們以前的同步的client相差不多.
wrapping up
我對于我們的第一個twisted client 沒有太多可說的,你應該注意到 connectionLostcallback在處理完所有的socket之后 關閉了這個reactor.這里并沒有用到什么技巧,因為client 假設我們在下載完詩后不會再做其他的事情,我們還可以看到其他的兩個底層的reactor api ,removeReader 和 getReaders.
在reactor api 中也還有Writer 相關的方法,被用來向文件描述符中寫一些東西,你可以在interfaces 中獲取更多信息.reader 和 writer 相關的api 會被twisted 分開寫,因為select loop 會分開這兩中事件.
在第五部分,我們將會用一些高級的api來寫我們的twisted poetry.學習更多的接口和api. 謝謝 ~~~~
總結
以上是生活随笔為你收集整理的python twised系列教程四–twisted Poetry client的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dpkg status database
- 下一篇: Python之collections模块