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在将来架构设计的决策上须要更积极的征求这类专家级用户的反馈意见。

相关文章
相关标签/搜索