原文連接:http://www.aosabook.org/en/twisted.htmlhtml
做者:Jessica McKellarjava
Twisted是用Python實現的基於事件驅動的網絡引擎框架。Twisted誕生於2000年初,在當時的網絡遊戲開發者看來,不管他們使用哪一種語言,手中都鮮有可兼顧擴展性及跨平臺的網絡庫。Twisted的做者試圖在當時現有的環境下開發遊戲,這一步走的很是艱難,他們迫切地須要一個可擴展性高、基於事件驅動、跨平臺的網絡開發框架,爲此他們決定本身實現一個,並從那些以前的遊戲和網絡應用程序的開發者中學習,汲取他們的經驗教訓。python
Twisted支持許多常見的傳輸及應用層協議,包括TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。就像Python同樣,Twisted也具備「內置電池」(batteries-included)的特色。Twisted對於其支持的全部協議都帶有客戶端和服務器實現,同時附帶有基於命令行的工具,使得配置和部署產品級的Twisted應用變得很是方便。react
21.1 爲何須要Twisted
2000年時,Twisted的做者Glyph正在開發一個名爲Twisted Reality的基於文本方式的多人在線遊戲。這個遊戲採用Java開發,裏面滿是一堆線程——每一個鏈接就有3個線程處理。處理輸入的線程會在讀操做上阻塞,處理輸出的線程將在一些寫操做上阻塞,還有一個「邏輯」線程將在等待定時器超時或者事件入隊列時休眠。隨着玩家們在虛擬世界中移動並交互時,線程出現死鎖,緩存被污染,程序中的加鎖邏輯幾乎歷來就沒對過——採用多線程使得整個軟件變得複雜、漏洞百出並且極難擴展。程序員
爲了尋求其餘的解決方案,做者發現了Python,特別是Python中用於對流式對象好比socket和pipe進行多路I/O複用的select模塊(UNIX規範第3版(SUSv3)描述了select)。那時,Java並無提供操做系統的select接口或者任何其餘的異步I/O API(針對非阻塞式I/O的包java.nio已經在J2SE 1.4中加入了,2002年發佈)。經過用Python中的select模塊快速搭建起遊戲的原型,這迅速下降了程序的複雜度,而且比多線程版本要更加可靠。web
Glyph迅速轉向了Python、select以及基於事件驅動的編程。他使用Python的select模塊爲遊戲編寫了客戶端和服務器。但他想要的還不止於此。從根本上說,他但願能將網絡行爲轉變爲對遊戲中的對象的方法調用。若是你能在遊戲中收取郵件會怎樣,就像Nethack mailer這種守護進程同樣?若是遊戲中的每位玩家都擁有一個主頁呢?Glyph發現他須要優秀的IMAP以及HTTP客戶端和服務器的Python實現,而這些都要採用select。數據庫
他首先轉向了Medusa,這是一個在90年代中期開發的平臺,在這裏能夠採用Python中的asyncore模塊來編寫網絡服務。asyncore是一個異步化處理socket的模塊,在操做系統的select API之上構建了一個調度器和回調接口。編程
這對於Glyph來講是個激動人心的發現,但Medusa有兩個缺點:設計模式
- 這個項目到2001年就再也不維護了,那正是glyph開發Twisted Reality的時候。
- asyncore只是對socket的一個薄封裝層,應用程序的編寫者仍然須要直接操做socket。這意味着程序可移植性的擔子仍然落在程序員本身身上。此外,那時asyncore對Windows的支持還有問題,Glyph但願能在Windows上運行一個帶有圖形用戶界面的客戶端。
Glyph須要本身實現一個網絡引擎平臺,並且他意識到Twisted Reality已經打開了問題的大門,這和他的遊戲同樣有趣。瀏覽器
隨着時間的推移,Twisted Reality這個遊戲就演化成了Twisted網絡引擎平臺。它能夠作到當時Python中已有的網絡平臺所沒法作到的事情:
- 使用基於事件驅動的編程模型,而不是多線程模型。
- 跨平臺:爲主流操做系統平臺暴露出的事件通知系統提供統一的接口。
- 「內置電池」的能力:提供流行的應用層協議實現,所以Twisted立刻就可爲開發人員所用。
- 符合RFC規範,已經經過健壯的測試套件證實了其一致性。
- 能很容易的配合多個網絡協議一塊兒使用。
- 可擴展。
21.2 Twisted架構概覽
Twisted是一個事件驅動型的網絡引擎。因爲事件驅動編程模型在Twisted的設計哲學中佔有重要的地位,所以這裏有必要花點時間來回顧一下究竟事件驅動意味着什麼。
事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。
讓咱們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。圖21.1展現了隨着時間的推移,這三種模式下程序所作的工做。這個程序有3個任務須要完成,每一個任務都在等待I/O操做時阻塞自身。阻塞在I/O操做上所花費的時間已經用灰色框標示出來了。
圖21.1 線程模型
在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。
在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是實現不當就會致使出現微妙且使人痛不欲生的bug。
在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序儘量的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。
當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇:
- 程序中有許多任務,並且…
- 任務之間高度獨立(所以它們不須要互相通訊,或者等待彼此)並且…
- 在等待事件到來時,某些任務會阻塞。
當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。
網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。
重用已有的應用
在Twisted建立以前就已經有了許多針對多種流行的網絡協議的客戶端和服務器實現了。爲何Glyph不直接用Apache、IRCd、BIND、OpenSSH或者任何其餘已有的應用,而要爲Twisted從頭開始從新實現各個協議的客戶端和服務器呢?
問題在於全部這些已有的實現都存在有從頭寫起的網絡層代碼,一般都是C代碼。而應用層代碼直接同網絡層耦合在一塊兒,這使得它們很是難以以庫的形式來複用。當要一塊兒使用這些組件時,若是但願在多個協議中暴露相同的數據,則它們必須以黑盒的形式來看待,這使得開發者根本沒機會重用代碼。此外,服務器和客戶端的實現一般是分離的,彼此之間不共享代碼。要擴展這些應用,維護跨平臺的客戶端-服務器兼容性的難度本不至於這麼大。
Twisted中的客戶端和服務器是用Python開發的,採用了一致性的接口。這使得開發新的客戶端和服務器變得很容易實現,能夠在客戶端和服務器之間共享代碼,在協議之間共享應用邏輯,以及對某個實現的代碼作測試。
Reactor模式
Twisted實現了設計模式中的反應堆(reactor)模式,這種模式在單線程環境中調度多個事件源產生的事件到它們各自的事件處理例程中去。
Twisted的核心就是reactor事件循環。Reactor能夠感知網絡、文件系統以及定時器事件。它等待而後處理這些事件,從特定於平臺的行爲中抽象出來,並提供統一的接口,使得在網絡協議棧的任何位置對事件作出響應都變得簡單。
基本上reactor完成的任務就是:
while True: timeout = time_until_next_timed_event() events = wait_for_events(timeout) events += timed_events_until(now()) for event in events: event.process()
Twisted目前在全部平臺上的默認reactor都是基於poll API的(UNIX規範第3版(SUSv3)中描述)。此外,Twisted還支持一些特定於平臺的高容量多路複用API。這些reactor包括基於FreeBSD中kqueue機制的KQueue reactor,支持epoll接口的系統(目前是Linux 2.6)中的epoll reactor,以及基於Windows下的輸入輸出完成端口的IOCP reactor。
在實現輪詢的相關細節中,Twisted須要考慮的包括:
- 網絡和文件系統的限制
- 緩衝行爲
- 如何檢測鏈接丟失
- 出現錯誤時的返回值
Twisted的reactor實現同時也考慮了正確使用底層的非阻塞式API,並正確處理各類邊界狀況。因爲Python中沒有暴露出IOCP API,所以Twisted須要維護本身的實現。
管理回調鏈
回調是事件驅動編程模型中的基礎,也是reactor通知應用程序事件已經處理完成的方式。隨着程序規模不斷擴大,基於事件驅動的程序須要同時處理事件處理成功和出錯的狀況,這使得程序變得愈來愈複雜。若沒有註冊一個合適的回調,程序就會阻塞,由於這個事件處理的過程毫不會發生。出現錯誤時須要經過應用程序的不一樣層次從網絡棧向上傳遞迴調鏈。
下面是兩段Python僞碼,分別是同步和異步模式下獲取URL的玩具代碼。讓咱們相互比較一下這兩個版本,看看基於事件驅動的程序有什麼缺陷:
以同步的方式獲取URL:
import getPage def processPage(page): print page def logError(error): print error def finishProcessing(value): print "Shutting down..." exit(0) url = "http://google.com" try: page = getPage(url) processPage(page) except Error, e: logError(error) finally: finishProcessing()
以異步的方式獲取URL:
from twisted.internet import reactor import getPage def processPage(page): print page finishProcessing() def logError(error): print error finishProcessing() def finishProcessing(value): print "Shutting down..." reactor.stop() url = "http://google.com" # getPage takes: url, # success callback, error callback getPage(url, processPage, logError) reactor.run()
在異步版的URL獲取器中,reactor.run()啓動reactor事件循環。在同步和異步版程序中,咱們假定getPage函數處理獲取頁面的工做。若是獲取成功就調用processPage,若是嘗試獲取頁面時出現了Exception(異常),logError就獲得調用。不管哪一種狀況,最後都要調用finishProcessing。
異步版中的logError回調正對應於同步版中的try/except塊。對processPage的回調對應於else塊,無條件回調的finishProcessing就對應於finally塊。
在同步版中,代碼結構直接顯示出有一個try/except塊,logError和processPage這二者間只會取其一調用一次,而finishProcessing老是會被調用一次。在異步版中須要由程序員本身負責正確調用成功和失敗狀況下的回調鏈。若是因爲編程錯誤,在processPage或者logError的回調鏈以後沒有調用finishProcessing,reactor事件循環將永遠不會中止,程序就會卡住。
這個玩具式的例子告訴咱們在開發Twisted的頭幾年裏這種複雜性令程序員感到很是沮喪。而Twisted應對這種複雜性的方式是新增一個稱爲Deferred(延遲)的對象。
Deferreds
Deferred對象以抽象化的方式表達了一種思想,即結果還尚不存在。它一樣可以幫助管理產生這個結果所須要的回調鏈。當從函數中返回時,Deferred對象承諾在某個時刻函數將產生一個結果。返回的Deferred對象中包含全部註冊到事件上的回調引用,所以在函數間只須要傳遞這一個對象便可,跟蹤這個對象比單獨管理全部的回調要簡單的多。
Deferred對象包含一對回調鏈,一個是針對操做成功的回調,一個是針對操做失敗的回調。初始狀態下Deferred對象的兩條鏈都爲空。在事件處理的過程當中,每一個階段都爲其添加處理成功的回調和處理失敗的回調。當一個異步結果到來時,Deferred對象就被「激活」,那麼處理成功的回調和處理失敗的回調就能夠以合適的方式按照它們添加進來的順序依次獲得調用。
異步版URL獲取器採用Deferred對象後的代碼以下:
from twisted.internet import reactor import getPage def processPage(page): print page def logError(error): print error def finishProcessing(value): print "Shutting down..." reactor.stop() url = "http://google.com" deferred = getPage(url) # getPage returns a Deferred deferred.addCallbacks(success, failure) deferred.addBoth(stop) reactor.run()
在這個版本中調用的事件處理函數與以前相同,但它們都註冊到了一個單獨的Deferred對象上,而不是分散在代碼各處再以參數形式傳遞給getPage。
Deferred對象建立時包含兩個添加回調的階段。第一階段,addCallbacks將 processPage和logError添加到它們各自歸屬的回調鏈中。而後addBoth再將finishProcessing同時添加到這兩個回調鏈上。用圖解的方式來看,回調鏈應該如圖21.2所示:
圖21.2 回調鏈
Deferred對象只能被激活一次,若是試圖重複激活將引起一個異常。這使得Deferred對象的語義至關接近於同步版中的try/except塊。從而讓異步事件的處理能更容易推斷,避免因爲針對單個事件的回調調用多了一個或少了一個而產生微妙的bug。
理解Deferred對象對於理解Twisted程序的執行流是很是重要的。然而當使用Twisted爲咱們提供的針對網絡協議的高層抽象時,一般狀況下咱們徹底不須要直接使用Deferred對象。
Deferred對象所包含的抽象概念是很是強大的,這種思想已經被許多其餘的事件驅動平臺所借用,包括jQuery、Dojo和Mochikit。
Transports
Transports表明網絡中兩個通訊結點之間的鏈接。Transports負責描述鏈接的細節,好比鏈接是面向流式的仍是面向數據報的,流控以及可靠性。TCP、UDP和Unix套接字可做爲transports的例子。它們被設計爲「知足最小功能單元,同時具備最大程度的可複用性」,並且從協議實現中分離出來,這讓許多協議能夠採用相同類型的傳輸。Transports實現了ITransports接口,它包含以下的方法:
write 以非阻塞的方式按順序依次將數據寫到物理鏈接上 writeSequence 將一個字符串列表寫到物理鏈接上 loseConnection 將全部掛起的數據寫入,而後關閉鏈接 getPeer 取得鏈接中對端的地址信息 getHost 取得鏈接中本端的地址信息
將transports從協議中分離出來也使得對這兩個層次的測試變得更加簡單。能夠經過簡單地寫入一個字符串來模擬傳輸,用這種方式來檢查。
Protocols
Protocols描述瞭如何以異步的方式處理網絡中的事件。HTTP、DNS以及IMAP是應用層協議中的例子。Protocols實現了IProtocol接口,它包含以下的方法:
makeConnection 在transport對象和服務器之間創建一條鏈接 connectionMade 鏈接創建起來後調用 dataReceived 接收數據時調用 connectionLost 關閉鏈接時調用
咱們最好以一個例子來講明reactor、protocols以及transports這三者之間的關係。如下是完整的echo服務器和客戶端的實現,首先來看看服務器部分:
from twisted.internet import protocol, reactor class Echo(protocol.Protocol): def dataReceived(self, data): # As soon as any data is received, write it back self.transport.write(data) class EchoFactory(protocol.Factory): def buildProtocol(self, addr): return Echo() reactor.listenTCP(8000, EchoFactory()) reactor.run()
接着是客戶端部分:
from twisted.internet import reactor, protocol class EchoClient(protocol.Protocol): def connectionMade(self): self.transport.write("hello, world!") def dataReceived(self, data): print "Server said:", data self.transport.loseConnection() def connectionLost(self, reason): print "connection lost" class EchoFactory(protocol.ClientFactory): def buildProtocol(self, addr): return EchoClient() def clientConnectionFailed(self, connector, reason): print "Connection failed - goodbye!" reactor.stop() def clientConnectionLost(self, connector, reason): print "Connection lost - goodbye!" reactor.stop() reactor.connectTCP("localhost", 8000, EchoFactory()) reactor.run()
運行服務器端腳本將啓動一個TCP服務器,監聽端口8000上的鏈接。服務器採用的是Echo協議,數據經TCP transport對象寫出。運行客戶端腳本將對服務器發起一個TCP鏈接,回顯服務器端的迴應而後終止鏈接並中止reactor事件循環。這裏的Factory用來對鏈接的雙方生成protocol對象實例。兩端的通訊是異步的,connectTCP負責註冊回調函數到reactor事件循環中,當socket上有數據可讀時通知回調處理。
Applications
Twisted是用來建立具備可擴展性、跨平臺的網絡服務器和客戶端的引擎。在生產環境中,以標準化的方式簡化部署這些應用的過程對於Twisted這種被普遍採用的平臺來講是很是重要的一環。爲此,Twisted開發了一套應用程序基礎組件,採用可重用、可配置的方式來部署Twisted應用。這種方式使程序員避免堆砌千篇一概的代碼來將應用程序同已有的工具整合在一塊兒,這包括精靈化進程(daemonization)、日誌處理、使用自定義的reactor循環、對代碼作性能剖析等。
應用程序基礎組件包含4個主要部分:服務(Service)、應用(Application)、配置管理(經過TAC文件和插件)以及twistd命令行程序。爲了說明這個基礎組件,咱們將上一節的Echo服務器轉變成一個應用。
Service
Service就是IService接口下實現的能夠啓動和中止的組件。Twisted自帶有TCP、FTP、HTTP、SSH、DNS等服務以及其餘協議的實現。其中許多Service均可以註冊到單獨的應用中。IService接口的核心是:
startService 啓動服務。可能包含加載配置數據,設定數據庫鏈接或者監聽某個端口 stopService 關閉服務。可能包含將狀態保存到磁盤,關閉數據庫鏈接或者中止監聽端口
咱們的Echo服務使用TCP協議,所以咱們可使用Twisted中IService接口下默認的TCPServer實現。
Application
Application是處於最頂層的Service,表明了整個Twisted應用程序。Service須要將其自身同Application註冊,而後就能夠用下面咱們將介紹的部署工具twistd搜索並運行應用程序。咱們將建立一個能夠同Echo Service註冊的Echo應用。
TAC文件
當在一個普通的Python文件中管理Twisted應用程序時,須要由開發者負責編寫啓動和中止reactor事件循環以及配置應用程序的代碼。在Twisted的基礎組件中,協議的實現都是在一個模塊中完成的,須要使用到這些協議的Service能夠註冊到一個Twisted應用程序配置文件中(TAC文件)去,這樣reactor事件循環和程序配置就能夠由外部組件來進行管理。
要將咱們的Echo服務器轉變成一個Echo應用,咱們能夠按照如下幾個簡單的步驟來完成:
-
將Echo服務器的Protocol部分移到它們本身所歸屬的模塊中去。
-
在TAC文件中:
- 建立一個Echo應用。
- 建立一個TCPServer的Service實例,它將使用咱們的EchoFactory,而後同前面建立的應用完成註冊。
管理reactor事件循環的代碼將由twistd來負責,咱們下面會對此進行討論。這樣,應用程序的代碼就變成這樣了:
echo.py文件:
from twisted.internet import protocol, reactor class Echo(protocol.Protocol): def dataReceived(self, data): self.transport.write(data) class EchoFactory(protocol.Factory): def buildProtocol(self, addr): return Echo()
twistd
twistd(讀做「twist-dee」)是一個跨平臺的用來部署Twisted應用程序的工具。它執行TAC文件並負責處理啓動和中止應用程序。做爲Twisted在網絡編程中具備「內置電池」能力的一部分,twistd自帶有一些很是有用的配置標誌,包括將應用程序轉變爲守護進程、定義日誌文件的路徑、設定特權級別、在chroot下運行、使用非默認的reactor,甚至是在profiler下運行應用程序。
咱們能夠像這樣運行這個Echo服務應用:
$ twistd –y echo_server.tac
在這個簡單的例子裏,twistd將這個應用程序做爲守護進程來啓動,日誌記錄在twistd.log文件中。啓動和中止應用後,日誌文件內容以下:
2011-11-19 22:23:07-0500 [-] Log opened. 2011-11-19 22:23:07-0500 [-] twistd 11.0.0 (/usr/bin/python 2.7.1) starting up. 2011-11-19 22:23:07-0500 [-] reactor class: twisted.internet.selectreactor.SelectReactor. 2011-11-19 22:23:07-0500 [-] echo.EchoFactory starting on 8000 2011-11-19 22:23:07-0500 [-] Starting factory <echo.EchoFactory instance at 0x12d8670> 2011-11-19 22:23:20-0500 [-] Received SIGTERM, shutting down. 2011-11-19 22:23:20-0500 [-] (TCP Port 8000 Closed) 2011-11-19 22:23:20-0500 [-] Stopping factory <echo.EchoFactory instance at 0x12d8670> 2011-11-19 22:23:20-0500 [-] Main loop terminated. 2011-11-19 22:23:20-0500 [-] Server Shut Down.
經過使用Twisted框架中的基礎組件來運行服務,這麼作使得開發人員可以不用再編寫相似守護進程和記錄日誌這樣的冗餘代碼了。這一樣也爲部署應用程序創建了一個標準的命令行接口。
Plugins
對於運行Twisted應用程序的方法,除了基於TAC文件外還有一種可選的方法,這就是插件系統。TAC系統能夠很方便的將Twisted預約義的服務同應用程序配置文件註冊,而插件系統可以方便的將用戶自定義的服務註冊爲twistd工具的子命令,而後擴展應用程序的命令行接口。
在使用插件系統時:
-
因爲只有plugin API須要保持穩定,這使得第三方開發者能很容易地擴展軟件。
-
插件發現能力已經集成到系統中了。插件能夠在程序首次運行時加載並保存,每次程序啓動時會從新觸發插件發現過程,或者也能夠在程序運行期間反覆輪詢新插件,這使得在程序已經啓動後咱們還能夠判斷是否有新的插件安裝上了。
當使用Twisted插件系統來擴展軟件時,咱們要作的就是建立IPlugin接口下實現的對象並將它們放到一個特定的位置中,這裏插件系統知道該如何去找到它們。
咱們已經將Echo服務轉換爲一個Twisted應用程序了,而將其轉換爲一個Twisted插件也是很是簡單直接的。在咱們以前的Echo模塊中,除了包含有Echo協議和EchoFactory的定義以外,如今咱們還要添加一個名爲twistd的目錄,其中還包含着一個名爲plugins的子目錄,這裏正是咱們須要定義echo插件的地方。經過這個插件,咱們能夠啓動一個echo服務,並將須要使用的端口號做爲參數指定給twistd工具。
from zope.interface import implements from twisted.python import usage from twisted.plugin import IPlugin from twisted.application.service import IServiceMaker from twisted.application import internet from echo import EchoFactory class Options(usage.Options): optParameters = [["port", "p", 8000, "The port number to listen on."]] class EchoServiceMaker(object): implements(IServiceMaker, IPlugin) tapname = "echo" description = "A TCP-based echo server." options = Options def makeService(self, options): """ Construct a TCPServer from a factory defined in myproject. """ return internet.TCPServer(int(options["port"]), EchoFactory()) serviceMaker = EchoServiceMaker()
如今,咱們的Echo服務器將做爲一個服務選項出如今twistd –help的輸出中。運行twistd echo –port=1235將在端口1235上啓動一個Echo服務器。
Twisted還帶有一個可拔插的針對服務器端認證的模塊twisted.cred,插件系統常見的用途就是爲應用程序添加一個認證模式。咱們可使用twisted.cred中現成的AuthOptionMixin類來添加針對各類認證的命令行支持,或者是添加新的認證類型。好比,咱們可使用插件系統來添加基於本地Unix密碼數據庫或者是基於LDAP服務器的認證方式。
twistd工具中附帶有許多Twisted所支持的協議插件,只用一條單獨的命令就能夠完成啓動服務器的工做了。這裏有一些經過twistd啓動服務器的例子:
twistd web –port 8080 –path .
這條命令將在8080端口啓動一個HTTP服務器,在當前目錄中負責處理靜態和動態頁面請求。
twistd dns –p 5553 –hosts-file=hosts
這條命令在端口5553上啓動一個DNS服務器,解析指定的文件hosts中的域名,這個文件的內容格式同/etc/hosts同樣。
sudo twistd conch –p tcp:2222
這條命令在端口2222上啓動一個SSH服務器。ssh的密鑰必須獨立設定。
twistd mail –E –H localhost –d localhost=emails
這條命令啓動一個ESMTP POP3服務器,爲本地主機接收郵件並保存到指定的emails目錄下。
咱們能夠方便的經過twistd來搭建一個用於測試客戶端功能的服務器,但它一樣是可裝載的、產品級的服務器實現。
在部署應用程序的方式上,Twisted經過TAC文件、插件以及命令行工具twistd的部署方式已經得到了成功。可是有趣的是,對於大多數大型Twisted應用程序來講,部署它們仍然須要重寫一些這類管理和監控組件;Twisted的架構並無對系統管理員的需求呈現出太多的友好性。這也反映了一個事實,那就是對於系統管理員來講Twisted從來就沒有太多架構可言,而這些系統管理員纔是部署和維護應用程序的專家。在這方面,Twisted在將來架構設計的決策上須要更積極的徵求這類專家級用戶的反饋意見。
21.3 反思與教訓
Twisted最近剛剛渡過了其10週年的誕辰。自項目成立以來,因爲受2000年早期的網絡遊戲啓發,目前的Twisted已經在很大程度上實現了做爲一個可擴展、跨平臺、事件驅動的網絡引擎的目標。Twisted普遍使用於生產環境中,從Google、盧卡斯電影到Justin.TV以及Launchpad軟件協做平臺都有在使用。Twisted中的服務器端實現是多個開源軟件的核心,包括BuildBot、BitTorrent以及TahoeLAFS。
Twisted從最初開發到如今,其架構已經經歷了幾回大的變更。Deferred對象做爲一個關鍵部分被增長了進來。如前文所述,這是用來管理延後的結果以及相應的回調鏈。
還有一個重要的部分被移除掉了,在目前的實現中已經幾乎看不到任何影子了,這就是Twisted應用持久化(Twisted Application Persistence)。
Twisted應用持久化
Twisted應用持久化(TAP)是指將應用程序的配置和狀態保存在一個pickle中。要運行採用了這種方案的應用須要兩個步驟:
-
使用mktap工具建立一個表明該應用的pickle(該工具現已廢棄不用)。
-
使用twistd命令行工具進行unpickle操做,而後運行該應用。
這個過程是受Smalltalk images的啓發,由於咱們討厭那種臨時性的且難以使用的專用配置語言,不但願它們在項目中不斷擴散。咱們更但願在Python中表示配置的細節。
很快,TAP文件就引入了沒必要要的複雜性。修改Twisted中的類並不會使pickle中這些類的實例獲得改變。在pickle對象上使用新版本的類方法或屬性時可能會使整個應用崩潰。所以「升級版」的概念得以引入,即將pickle對象升級到新的API版本。但這就會出現升級版本的矩陣化現象,出現各類不一樣版本的pickle對象,所以單元測試時須要維護涵蓋全部可能的升級路徑。想全面地跟蹤全部的接口變化依然很難,並且容易出錯。
TAP以及相關的組件所有被廢除了,最終從Twisted中徹底剔除掉。取而代之的是TAC文件和插件系統。TAP這個縮寫被從新定義爲Twisted Application Plugin(Twisted應用插件),現在已經很難在Twisted中找到pickle系統的蹤影了。
咱們從TAP的慘敗中獲得的教訓是:若是可維護性要達到合理化的程度,則持久性數據就須要有一個明確的模式。更通常的是,咱們學到了如何爲項目增長複雜度:爲了解決某個問題而須要引入一個新系統時,咱們要正確理解這個方案的複雜性,並通過測試。新系統所帶來的價值應該明顯大於其複雜性。確保了這一點以後咱們才能將方案付諸於項目中。
web2:重構的教訓
雖然這基本上不屬於架構設計上的決策,但從項目管理的角度來看,重寫Twisted的Web實現對於Twisted的外在形象以及維護者對代碼庫中其餘部分作架構改善的能力卻有着長遠的影響,所以這裏值得咱們簡單討論一下。
在2000年中期,Twisted的開發者決定徹底重寫twisted.web API,在Twisted代碼庫中將其做爲一個單獨的項目實現,這就是web2。web2將包含許多針對原有twisted.web的改善和提高,包括徹底支持HTTP1.1,以及對流式數據的API支持。
web2最初只是試驗性的項目,但最終被大型項目所採用,甚至意外的得以在Debian系統上打包發佈。twisted.web和web2的開發一直並行持續了多年,新用戶經常被這兩個並行的項目搞混,關於究竟應該使用哪一種實現缺少明確的提示,這使得新用戶很沮喪。轉換到web2的狀況從未出現,終於在2011年開發者將其從代碼庫中移除,官方主頁上再也看不到它了。web2中作出的一些改進也被慢慢地移植回twisted.web中。
Twisted得到了難以導航且結構混亂,容易使新開發者感到困惑的「惡名」,這個印象部分歸功於web2。以致於數年以後,Twisted社區仍然在同這種不和諧的名聲作鬥爭。
咱們從web2中汲取的教訓是:從頭開始重構一個項目一般都是糟糕的主意。但若是必須這麼作,請確保開發者社區可以懂得這麼作的長遠意義,並且在用戶社羣中要有明確的選擇該使用哪一種實現。
若是Twisted可以倒退回web2的時代,開發者們應該會對twisted.web作一系列向後兼容型的修改而不是去重構。
緊跟互聯網的浪潮
咱們使用互聯網的方式還在持續演進中。把多種協議的實現做爲軟件核心的一部分,這個技術決策使得Twisted揹負了維護這些協議的沉重負擔。隨着標準的改變以及對新協議的採納,原有的實現必須跟着演進,同時須要嚴格的保證向後兼容性。
Twisted基本上是一個志願者驅動型的項目,項目發展的限制因素不是技術社區的熱情,而在於志願者的時間。好比說,1999年的RFC 2616中定義了HTTP 1.1規範,而在Twisted的HTTP協議實現中增長對HTTP 1.1的支持卻在2005年纔開始,等到完成時已是2009年了。1998年RFC 2460中定義了對IPv6的支持,而Twisted對其的支持還在進行中,可是直到2011年都未能合併進去。
隨着所支持的操做系統的接口改變,實現也要跟着演進。好比,epoll事件通知機制是在2002年加入到Linux 2.5.44版中的,Twisted隨之也發展出基於epoll的reactor事件循環來利用這個新的系統接口。2007年時,蘋果公司發佈的OS 10.5 Leopard系統中,系統調用poll的實現竟然不支持外設,對於蘋果公司來講這個問題足以讓他們在系統自帶的Python中屏蔽掉select.poll接口。Twisted不得不自行解決這個問題,並從那時起就對用戶提供文檔說明。
有時候,Twisted的開發並無緊跟網絡世界的變化,有一些改進被移到核心層以外的程序庫中去了。好比Wokkel project,這是對Twisted的Jabber/XMPP支持的改進合集,已經做爲「待合入」的獨立項目有幾年之久了,但尚未看到合入的但願。在2009年也曾經嘗試過增長WebSocket到Twisted中,由於瀏覽器已經開始採納對新協議的支持了。但開發計劃最終卻轉到其餘外部項目中去了,由於開發者們決定暫不包含新的協議,直到IETF把它從草案轉變成標準之後再說。
全部這一切都在說明,庫和附加組件的擴散有力的證實了Twisted的靈活性和可擴展性。經過採用嚴格的測試驅動開發策略以及文檔化和編碼規範標準,這樣作可以幫助項目避免出現須要「回爐」的狀況。在維護大量所支持的協議和平臺的同時保持向後兼容性。Twisted是一個成熟、穩定的項目,並繼續保持有很是活躍的開發狀態。
Twisted期待着在下一個十年裏成爲你遨遊互聯網的引擎。