Python的Twisted事件驅動的網絡引擎框架

Python的Twisted事件驅動的網絡引擎框架

概述

Twisted是用Python實現的基於事件驅動的網絡引擎框架。Twisted支持許多常見的傳輸及應用層協議,包括TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。python

優越性

  • 使用基於事件驅動的編程模型,而不是多線程模型。
  • 跨平臺:爲主流操做系統平臺暴露出的事件通知系統提供統一的接口。
  • 「內置電池」的能力:提供流行的應用層協議實現,所以Twisted立刻就可爲開發人員所用。
  • 符合RFC規範,已經經過健壯的測試套件證實了其一致性。
  • 能很容易的配合多個網絡協議一塊兒使用。
  • 可擴展。

事件驅動編程

事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。事件驅動模型也就是咱們常說的觀察者,或者發佈-訂閱模型;理解它的幾個關鍵點:react

  • 一種對象間的一對多的關係;最簡單的如交通訊號燈,信號燈是目標(一方),行人注視着信號燈(多方);程序員

  • 當目標發送改變(發佈),觀察者(訂閱者)就能夠接收到改變;web

  • 觀察者如何處理(如行人如何走,是快走/慢走/不走,目標不會管的),目標無需干涉;因此就鬆散耦合了它們之間的關係。數據庫

下圖表現了單線程、多線程和事件驅動的關係:編程

  • 在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。
  • 在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是實現不當就會致使出現微妙且使人痛不欲生的bug。
  • 在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序儘量的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。

什麼狀況可使用事件驅動

  1. 程序中有許多任務
  2. 任務之間高度獨立
  3. 在等待事件到來時,某些任務會阻塞。

一句話,不須要同步處理的多任務處理就可使用事件驅動了。設計模式

Twisted

Twisted中的客戶端和服務器是用Python開發的,採用了一致性的接口。這使得開發新的客戶端和服務器變得很容易實現,能夠在客戶端和服務器之間共享代碼,在協議之間共享應用邏輯,以及對某個實現的代碼作測試。Twisted採用了Reactor設計模式,其核心就是Reactor的事件循環。Reactor能夠感知網絡、文件系統以及定時器事件。它等待而後處理這些事件,從特定於平臺的行爲中抽象出來,並提供統一的接口,使得在網絡協議棧的任何位置對事件作出響應都變得簡單。安全

下面以獲取一個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()

一異步的方式獲取以下:網絡

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()

從上面異步代碼段能夠看出,若是編寫丟失了reactor.stop()就會進入死循環,Twisted應對這種複雜性的方式是新增一個稱爲Deferred(延遲)的對象。

Deferreds

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對象建立時包含兩個添加回調的階段。第一階段,addCallbacks將 processPage和logError添加到它們各自歸屬的回調鏈中。而後addBoth再將finishProcessing同時添加到這兩個回調鏈上。Deferred對象只能被激活一次,若是試圖重複激活將引起一個異常.用圖解的方式來看,回調鏈應該如圖所示:

Transports

Transports表明網絡中兩個通訊結點之間的鏈接。Transports負責描述鏈接的細節,好比鏈接是面向流式的仍是面向數據報的,流控以及可靠性。TCP、UDP和Unix套接字可做爲transports的例子。Transports實現了ITransports接口

write                   以非阻塞的方式按順序依次將數據寫到物理鏈接上
writeSequence           將一個字符串列表寫到物理鏈接上
loseConnection          將全部掛起的數據寫入,而後關閉鏈接
getPeer                 取得鏈接中對端的地址信息
getHost                 取得鏈接中本端的地址信息

Protocols

Protocols描述瞭如何以異步的方式處理網絡中的事件。HTTP、DNS以及IMAP是應用層協議中的例子。Protocols實現了IProtocol接口,它包含以下的方法:

makeConnection               在transport對象和服務器之間創建一條鏈接
connectionMade               鏈接創建起來後調用
dataReceived                 接收數據時調用
connectionLost               關閉鏈接時調用

詳細的 reactor、transport、protocols例子

運行服務器端腳本將啓動一個TCP服務器,監聽端口8000上的鏈接。服務器採用的是Echo協議,數據經TCP transport對象寫出。運行客戶端腳本將對服務器發起一個TCP鏈接,回顯服務器端的迴應而後終止鏈接並中止reactor事件循環。這裏的Factory用來對鏈接的雙方生成protocol對象實例。兩端的通訊是異步的,connectTCP負責註冊回調函數到reactor事件循環中,當socket上有數據可讀時通知回調處理。

  • 服務器部分

    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()

Applications

Twisted是用來建立具備可擴展性、跨平臺的網絡服務器和客戶端的引擎。Applications基礎組件包含4個主要部分:服務(Service)、應用(Application)、配置管理(經過TAC文件和插件)以及twistd命令行程序。爲了說明這個基礎組件,咱們將上一節的Echo服務器轉變成一個應用。

Service

IService接口下實現的能夠啓動和中止的組件。Twisted自帶有TCP、FTP、HTTP、SSH、DNS等服務以及其餘協議的實現。其中許多Service均可以註冊到單獨的應用中。IService接口的核心是:

startService    啓動服務。可能包含加載配置數據,設定數據庫鏈接或者監聽某個端口
stopService     關閉服務。可能包含將狀態保存到磁盤,關閉數據庫鏈接或者中止監聽端口

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在將來架構設計的決策上須要更積極的徵求這類專家級用戶的反饋意見。

相關文章
相關標籤/搜索