Twisted入門教程(5)

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

第五部分:由Twited支持的詩歌下載服務客戶端

你可以從這里從頭開始閱讀這個系列

?

抽象地構建客戶端

在第四部分中,我們構建了第一個使用Twisted的客戶端。它確實能很好地工作,但仍有提高的空間。

首先是,這個客戶端竟然有創建網絡端口并接收端口處的數據這樣枯燥的代碼。Twisted理應為我們實現這些例程性功能,省得我們每次寫一個新的程序時都要去自己實現。Twisted這樣做也將我們從像異步I/O操作中包括許多像異常處理這樣的細節處理解放出來。更多的細節處理存在于多平臺上運行我們的代碼中。如果你那個下午有空,可以翻翻Twisted的WIN32實現源代碼,看看里面有多少小針線是來處理跨平臺的。

另一問題是與錯誤處理有關。當運行版本1的Twisted客戶端來從并沒有提供服務的端口上下載詩歌時,它就會崩潰。我們是可以修正這個錯誤,但通過下面我們要介紹Twisted的APIs來處理這些類型的錯誤會更簡單。

最后,那個客戶端也不能復用。如果有另一個模塊需要通過我們的客戶端下載詩歌呢?人家怎么知道你的詩歌已經下載完畢?我們不能用一個方法簡單地將一首詩下載完成后再傳給人家,而在之前讓人家處于等待狀態。這確實是一個問題,但我們不準備在這個部分解決這個問題—在未來的部分中一定會解決這個問題。

我們將會使用一些高層次的APIs和接口來解決第一、二個問題。Twisted框架是由眾多抽象層松散地組合起來的。因此,學習Twisted也就意味著需要學習這些層都提供什么功能,例如每層都有哪些APIs,接口和實例可供使用。接下來我們會通過剖析Twisted最最重要的部分來更好地感受一下Twisted都是怎么組織的。一旦你對Twisted的整個結構熟悉了,學習新的部分會簡單多了。

一般來說,每個Twisted的抽象都只與一個特定的概念相關。例如,第四部分中的客戶端使用的IReadDescriptor,它就是“一個可以讀取字節的文件描述符”的抽象。一個抽象往往會通過定義接口來指定那些想實現個抽象(也就是實現這個接口)對象的形為。在學習新的Twisted抽象概念時,最需要謹記的就是:

多數高層次抽象都是在低層次抽象的基礎上建立的,很少有另立門戶的。

因此,你在學習新的Twisted抽象概念時,始終要記住它做什么和不做什么。特別是,如果一個早期的抽象A實現了F特性,那么F特性不太可能再由其它任何抽象來實現。另外,如果另外一個抽象需要F特性,那么它會使用A而不是自己再去實現F。(通常的做法,B可能會通過繼承A或獲得一個指向A實例的引用)

網絡非常的復雜,因此Twisted包含很多抽象的概念。通過從低層的抽象講起,我們希望能更清楚起看到在一個Twisted程序中各個部分是怎么組織起來的。

?

核心的循環體

第一個我們要學習的抽象,也是Twisted中最重要的,就是reactor。在每個通過Twisted搭建起來的程序中心處,不管你這個程序有多少層,總會有一個reactor循環在不停止地驅動程序的運行。再也沒有比reactor提供更加基礎的支持了。實際上,Twisted的其它部分(即除了reactor循環體)可以這樣理解:它們都是來輔助X來更好地使用reactor,這里的X可以是提供Web網頁、處理一個數據庫查詢請求或其它更加具體內容。盡管堅持像上一個客戶端一樣使用低層APIs是可能的,但如果我們執意那樣做,那么我們必需自己來實現非常多的內容。而在更高的層次上,意味著我們可以少寫很多代碼。

但是當在外層思考與處理問題葉。很容易就忘記了reactor的存在了。在任何一個常見大小的Twisted程序中 ,確實很少會有直接與reactor的APIs交互。低層的抽象也是一樣(即我們很少會直接與其交互)。我們在上一個客戶端中用到的文件描述符抽象,就被更高層的抽象更好的歸納而至于我們很少會在真正的Twisted程序中遇到。(他們在內部依然在被使用,只是我們看不到而已)

至于文件描述符抽象的消息,這并不是一個問題。讓Twisted掌舵異步I/O處理,這樣我們就可以更加關注我們實際要解決的問題。但對于reactor不一樣,它永遠都不會消失。當你選擇使用Twisted,也就意味著你選擇使用Reactor模式,并且意味著你需要使用回調與多任務合作的“交互式”編程方式。如果你想正確地使用Twisted,你必須牢記reactor的存在。我們將在第六部分更加詳細的講解部分內容。但是現在要強調的是:

圖5與圖6是這個系列中最最重要的圖

我們還將用圖來描述新的概念,但這兩個圖是需要你牢記在腦海中的。可以這樣說,我在寫Twisted程序時一直想著這兩張圖。

在我們付諸于代碼前,有三個新的概念需要闡述清楚:Transports,Protocols,Protocol Factoies

?

Transports

Transports抽象是通過Twisted中interfaces模塊中ITransport接口定義的。一個Twisted的Transport代表一個可以收發字節的單條連接。對于我們的詩歌下載客戶端而言,就是對一條TCP連接的抽象。但是Twisted也支持諸如Unix中管道和UDP。Transport抽象可以代表任何這樣的連接并為其代表的連接處理具體的異步I/O操作細節。

如果你瀏覽一下ITransport中的方法,可能找不到任何接收數據的方法。這是因為Transports總是在低層完成從連接中異步讀取數據的許多細節工作,然后通過回調將數據發給我們。相似的原理,Transport對象的寫相關的方法為避免阻塞也不會選擇立即寫我們要發送的數據。告訴一個Transport要發送數據,只是意味著:盡快將這些數據發送出去,別產生阻塞就行。當然,數據會按照我們提交的順序發送。

通常我們不會自己實現一個Transport。我們會去實現Twisted提供的類,即在傳遞給reactor時會為我們創建一個對象實例。

?

Protocols

Twisted的Protocols抽象由interfaces模塊中的IProtocol定義。也許你已經想到,Protocol對象實現協議內容。也就是說,一個具體的Twisted的Protocol的實現應該對應一個具體網絡協議的實現,像FTP、IMAP或其它我們自己規定的協議。我們的詩歌下載協議,正如它表現的那樣,就是在連接建立后將所有的詩歌內容全部發送出去并且在發送完畢后關閉連接。

嚴格意義上講,每一個Twisted的Protocols類實例都為一個具體的連接提供協議解析。因此我們的程序每建立一條連接(對于服務方就是每接受一條連接),都需要一個協議實例。這就意味著,Protocol實例是存儲協議狀態與間斷性(由于我們是通過異步I/O方式以任意大小來接收數據的)接收并累積數據的地方。

因此,Protocol實例如何得知它為哪條連接服務呢?如果你閱讀IProtocol定義會發現一個makeConnection函數。這是一個回調函數,Twisted會在調用它時傳遞給其一個也是僅有的一個參數,即就是Transport實例。這個Transport實例就代表Protocol將要使用的連接。

Twisted包含很多內置可以實現很多通用協議的Protocol。你可以在twisted.protocols.basic找到一些稍微簡單點的。在你嘗試寫新Protocol時,最好是看看Twisted源碼是不是已經有現成的存在。如果沒有,那實現一個自己的協議是非常好的,正如我們為詩歌下載客戶端做的那樣。

?

Protocol Factories

因此每個連接需要一個自己的Portocol,而且這個Protocol是我們自己定義類的實例。由于我們會將創建連接的工作交給Twisted來完成,Twisted需要一種方式來為一個新的連接制定一個合適的協議。制定協議就是Protocol Factories的 工作了。

也許你已經猜到了,Protocol Factory的API由IProtocolFactory來定義,同樣在interfaces模塊中。Protocol Factory就是Factory模式的一個具體實現。buildProtocol方法在每次被調用時返回一個新Protocol實例。它就是Twisted用來為新連接創建新Protocol實例的方法。

?

詩歌下載客戶端2.0:第一滴心血

好吧,讓我們來看看由Twisted支持的詩歌下載客戶端2.0。源碼可以在這里twisted-client-2/get-poetry.py。你可以像前面一樣運行它,并得到相同的輸出。這也是最后一個在接收到數據時打印其任務的客戶端版本了。到現在為止,對于所有Twisted程序都是交替執行任務并處理相對較少數量數據的,應該很清晰了。我們依然通過print函數來展示在關鍵時刻在進行什么內容,但將來客戶端不會在這樣繁鎖。

在第二個版本中,sockets不會再出現了。我們甚至不需要引入socket模塊也不用引用socket對象和文件描述符。取而代之的是,我們告訴reactor來創建到詩歌服務器的連接,代碼如下面所示:

factory = PoetryClientFactory(len(addresses))

?

from twisted.internet import reactor

?

for address in addresses:

????host, port = address

????reactor.connectTCP(host, port, factory)

我們需要關注的是connectTCP這個函數。前兩個參數的含義很明顯,不解釋了。第三個參數是我們自定義的PoetryClientFactory類的實例對象。這是一個專門針對詩歌下載客戶端的Protocol Factory,將它傳遞給reactor可以讓Twisted為我們創建一個PeotryProtocol實例。

值得注意的是,從一開始我們既沒有實現Factory也沒有去實現Protocol,不像在前面那個客戶端中我們去實例化我們PoetrySocket類。我們只是繼承了Twisted在twisted.internet.protocol?中提供的基類Factory的基類是twisted.internet.protocol.Factory但我們使用客戶端專用(即不像服務器端那樣監聽一個連接,而是主動創建一個連接)的ClientFactory子類來繼承。

我們同樣利用了TwistedFactory已經實現了buildProtocol方法這一優勢來為我們所用。我們要在子類中調用基類中的實現:

def buildProtocol(self, address):

????proto = ClientFactory.buildProtocol(self, address)

????proto.task_num = self.task_num

????self.task_num += 1

????return proto

基類怎么會知道我們要創建什么樣的Protocol呢?注意,我們的PoetryClientFactory中有一個protocol類變量:

class PoetryClientFactory(ClientFactory):

?

????task_num = 1

?

????protocol = PoetryProtocol # tell base class what proto to build

基類Factory的實現buildProtocol過程是:安裝(創建一個實例)我們設置在protocol變量上的Protocol類與在這個實例(此處即PoetryProtocol的實例)的factory屬性上設置一個產生它的Factory的引用(此處即實例化PoetryProtocol的PoetryClientFactory)。這個過程如圖8所示:

第五部分:由Twisted支持的詩歌客戶端
?

圖8:Protocol的生成過程

正如我們提到的那樣,位于Protocol對象內的factory屬性字段允許在都由同一個factory產生的Protocol之間共享數據。由于Factories都是由用戶代碼來創建的(即在用戶的控制中),因此這個屬性也可以實現Protocol對象將數據傳遞回一開始初始化請求的代碼中來,這將在第六部分看到。

值得注意的是,雖然在Protocol中有一個屬性指向生成其的Protocol Factory,在Factory中也有一個變量指向一個Protocol類,但通常來說,一個Factory可以生成多個Protocol。

在Protocol創立的第二步便是通過makeConnection與一個Transport聯系起來。我們無需自己來實現這個函數而使用Twisted提供的默認實現。默認情況是,makeConnection將Transport的一個引用賦給(Protocol的)transport屬性,同時置(同樣是Protocol的)connected屬性為True,正如圖9描述的一樣:

第五部分:由Twisted支持的詩歌客戶端
?

圖9:Protocol遇到其Transport

一旦初始化到這一步后,Protocol開始其真正的工作—將低層的數據流翻譯成高層的協議規定格式的消息。處理接收到數據的主要方法是dataReceived,我們的客戶端是這樣實現的:

def dataReceived(self, data):

????self.poem += data

????msg = 'Task %d: got %d bytes of poetry from %s'

????print??msg % (self.task_num, len(data), self.transport.getHost())

每次dateReceved被調用就意味著我們得到一個新字符串。由于與異步I/O交互,我們不知道能接收到多少數據,因此將接收到的數據緩存下來直到完成一個完整的協議規定格式的消息。在我們的例子中,詩歌只有在連接關閉時才下載完畢,因此我們只是不斷地將接收到的數據添加到我們的.poem屬性字段中。

注意我們使用了Transport的getHost方法來取得數據來自的服務器信息。我們這樣做只是與前面的客戶端保持一致。相反,我們的代碼沒有必要這樣做,因為我們沒有向服務器發送任何消息,也就沒有必要知道服務器的信息了。

我們來看一下dataReceved運行時的快照。在2.0版本相同的目錄下有一個twisted-client-2/get-poetry-stack.py。它與2.0版本的不同之處只在于:

def dataReceived(self, data):

????traceback.print_stack()

????os._exit(0)

這樣一改,我們就能打印出跟蹤堆棧的信息,然后離開程序,可以用下面的命令來運行它:

python twisted-client-2/get-poetry-stack.py 10000

你會得到內容如下的跟蹤堆棧:

File "twisted-client-2/get-poetry-stack.py", line 125, in
poetry_main()?
??... # I removed a bunch of lines here?
??File ".../twisted/internet/tcp.py", line 463, in doRead # Note the doRead callback?
return self.protocol.dataReceived(data)?
File "twisted-client-2/get-poetry-stack.py", line 58, in dataReceived?traceback.print_stack()看見沒,有我們在1.0版本客戶端的doRead回調函數。我們前面也提到過,Twisted在建立新抽象層進會使用已有的實現而不是另起爐灶。因此必然會有一個IReadDescriptor的實例在辛苦的工作,它是由Twisted代碼而非我們自己的代碼來實現。如果你表示懷疑,那么就看看twisted.internet.tcp中的實現吧。如果你瀏覽代碼會發現,由同一個類實現了IWriteDescriptor與ITransport。因此?IreadDescriptor實際上就是變相的Transport類。可以用圖10來形象地說明dateReceived的回調過程:?
?圖10:dateReceived回調過程?
一旦詩歌下載完成,PoetryProtocol就會通知它的PooetryClientFactory:?
def connectionLost(self, reason):?????
??self.poemReceived(self.poem)?
def poemReceived(self, poem):????
??self.factory.poem_finished(self.task_num, poem)

當transport的連接關閉時,conncetionLost回調會被激活。reason參數是一個twisted.python.failure.Failure的實例對象,其攜帶的信息能夠說明連接是被安全的關閉還是由于出錯被關閉的。我們的客戶端因認為總是能完整地下載完詩歌而忽略了這一參數。

工廠會在所有的詩歌都下載完畢后關閉reactor。再次重申:我們代碼的工作就是用來下載詩歌-這意味我們的PoetryClientFactory缺少復用性。我們將在下一部分修正這一缺陷。值得注意的是,poem_finish回調函數是如何通過跟蹤剩余詩歌數的:

...

????self.poetry_count -= 1

?

????if self.poetry_count == 0:

????????…

如果我們采用多線程以讓每個線程分別下載詩歌,這樣我們就必須使用一把鎖來管理這段代碼以免多個線程在同一時間調用poem_finish。但是在交互式體系下就不必擔心了。由于reactor只能一次啟用一個回調。

新的客戶端實現在處理錯誤上也比先前的優雅的多,下面是PoetryClientFactory處理錯誤連接的回調實現代碼:

def clientConnectionFailed(self, connector, reason):

????print 'Failed to connect to:', connector.getDestination()

????self.poem_finished()

注意,回調是在工廠內部而不是協議內部實現。由于協議是在連接建立后才創建的,而工廠能夠在連接未能成功建立時捕獲消息。


?

結束語:

版本2的客戶端使用的抽象對于那些Twisted高手應該非常熟悉。如果僅僅是為在命令行上打印出下載的詩歌這個功能,那么我們已經完成了。但如果想使我們的代碼能夠復用,能夠被內嵌在一些包含詩歌下載功能并可以做其它事情的大軟件中,我們還有許多工作要做,我們將在第六部分講解相關內容。

?

登錄樂搏學院官網http://www.learnbo.com/

或關注我們的官方微博微信,還有更多驚喜哦~

轉載于:https://my.oschina.net/learnbo/blog/838408

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/457198.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/457198.shtml
英文地址,請注明出處:http://en.pswp.cn/news/457198.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jquery 學習之基礎一

1.添加一個CSS類 $("button").click(function(){ $("#div1").addClass("important blue");}); 2.移除一個類 $("button").click(function(){ $("h1,h2,p").removeClass("blue");}); 3.切換類 $("button&…

**print('人生苦短 我愛Python')**

print(‘人生苦短 我愛Python’) 一、變量 **""" 1.代碼自上而下執行 2_運算符和表達式.一行一句,不要把多個語句寫到一行上,可讀性不好 3中文只能出現在引號里,其他地方不能出現中文 4不能隨意縮進 """**pr…

java線程提高速度_如何在JAVA中減慢線程速度

我有這個類,我在其中運行10次for循環.該類實現了Runnable接口.現在在main()中我創建了2個線程.現在兩個都將循環運行到10.但我想檢查每個線程的循環計數.如果t1超過7,則讓它休眠1秒,以便讓t2完成.但是如何實現這一目標呢?請參閱代碼.我嘗試但看起來完全愚蠢.只是如何…

(轉ORCLE導入導出命令)

oracle數據庫導入導出命令!Oracle數據導入導出imp/exp 功能:Oracle數據導入導出imp/exp就相當與oracle數據還原與備份。 大多情況都可以用Oracle數據導入導出完成數據的備份和還原(不會造成數據的丟失)。 Oracle有個好處&…

筆記本(華碩UL80VT)軟件超頻setFSB

Warning !!!If you are a beginner, do not use this software. This software is for power users only. Use "SetFSB.exe" at your own risk.試了setfsb各種版本,基本不能打開。還有官網的免費版,居然不能用,真是很奇怪。 官網&a…

Day01-python編程基礎

1. 程序 為了完成某種特定功能,以某種程序設計語言編寫的有序指令的集合。程序是指揮cpu工作的“工作手冊”。計算機只能執行二進制代碼,程序設計語言一般類似英文,想要讓計算機理解你寫的程序,必須把程序代碼“翻譯”成計算機能…

pro c 訪問 MySQL_Pro*C??OCI??OCCI??及OCI介紹

Pro*C OCIOCCI 這三種都是C/C訪問數據庫的手段。Pro*C:內嵌SQL,預編譯后把內嵌SQL處理為ORACLE標準運行庫的調用,然后再象編譯一般的C程序一樣進行編譯、連接、運行。1.proc XXX.pc2.gcc/g XXX.c –lclntsh(PRO*C的預編譯,也可直接MAKEFILE…

細說Cookie

http://www.cnblogs.com/fish-li/archive/2011/07/03/2096903.html轉載于:https://www.cnblogs.com/benpaodexiaopangzi/p/5933230.html

Node.js~在linux上的部署

我們以centOS為例來說說如何部署node.js環境 一 打開centos,然后開始下載node.js包 curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - yum -y install nodejs 二 安裝gcc環境 yum install gcc-c make 安裝完成! 三 安裝nodejs的npm,這是一個包程序工具…

Django中的class Meta知識點

今天來回顧一下Django中model的class Meta class Main(models.Model):img models.CharField(max_length200) # 圖片name models.CharField(max_length100) # 名稱trackid models.CharField(max_length16) # 通用idclass Meta:abstract True #抽象類class MainWheel(…

python wheel使用_Python wheel.Wheel方法代碼示例

# 需要導入模塊: from pip import wheel [as 別名]# 或者: from pip.wheel import Wheel [as 別名]def from_line(cls, name, comes_fromNone, prereleasesNone):"""Creates an InstallRequirement from a name, which might be arequirement, directory contai…

英語學習Day1

今天要開始學英語了,我這個人沒什么長性,語言這種東西對我來說又是很不擅長的一種技能,那為什么要學英語呢?我要做翻譯,也是呵呵了,主要是我想賺錢,這個力量能夠支撐我吧,我想。 網上…

LeetCode題解-3-Longest Substring Without Repeating Characters

2019獨角獸企業重金招聘Python工程師標準>>> 解題思路 首先要讀懂題目,它要求的是找到最長的子串,并且子串中沒有出現重復的字符。 我的想法,是用一個map存儲每個字符最后出現的位置,還要有個變量start,它用…

Day02-變量_數據類型和運算符

1 數據類型 不同類型的變量可以進行的運算是不同的,所以必須理解變量的類型,python中數據類型可以分為: 內置類型 數值類型:整型int,浮點型float,復數(complex) 35j str:字符串 b…

css hack

http://blog.csdn.net/swallowliyan/article/details/50803469轉載于:https://www.cnblogs.com/annie211/p/5934035.html

java從哪學到哪_Java JVM怎么學習啊?從哪方面入手?

叮當貓咪一、 JVM的生命周期  1. JVM實例對應了一個獨立運行的java程序它是進程級別  a) 啟動。啟動一個Java程序時,一個JVM實例就產生了,任何一個擁有public static void main(String[] args)函數的class都可以作為JVM實例運行的起點  b) 運行。m…

JMeter處理Cookie與Session

cookie 和session 的區別: 1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。 2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙 考慮到安全應當使用session。 3、session會在一定時間內保存在服務器上。當…

Maximum sum(poj 2479)

題意:給一段數列,將這個數列分成兩部分,使兩部分的最大子段和的和最大,輸出和/*看數據沒想到是(O)n的算法,求出從前向后的最大子段和和從后向前的最大子段和,然后枚舉斷點。 第一次提交不小心折在數組最小值…

java futuretask 實例_java 使用Callable+FutureTask獲取執行結果

之前給大家分享了使用CallableFuture獲取執行結果的java示例,那么下面要給大家分享的就是使用CallableFutureTask獲取執行結果的java示例,一起來看看吧。public class Test{public static void main(String[] args){//方式1ExecutorService executor Ex…

Day03-運算符和表達式

運算符和表達式? 1. 運算符的優先級 盡量不要把一個表達式寫的過于復雜,如果遇到復雜的需求,則最好分步運算 不要過多的依賴于運算符的優先級,否則代碼的可讀性太差,在實際的項目開發中,一般采用(&#…