[Python] Twiested - 基于事件驱动的网络编程

长平狐 发布于 2013/06/03 15:43
阅读 143
收藏 0

介绍 

twisted 是python下一个事件驱动的网络引擎库, 支持很多种的协议.
它包含了一个web服务, 多种IM客户端,服务端, 邮件服务协议. 由于规模庞大, twisted分成了几个sub-project. 一起或者分开发布.

稳定性

twisted并不是从0.1发展到8.1.0的. 是一下子跳到8的. 8.1.0是最新的stable的包.
从successful stories来看, 它的客户包括NASA这种级别的(http://twistedmatrix.com/trac/wiki/SuccessStories#NASA).

效率

twisted具有惊人的效率.

入门教程

Twisted应用的基本问题,可说是“一个中心,两个基本点”,即: 
以“事件”event为中心,以"建立连接"connect和“定义反馈“callback为基本点。 

在这些问题上,Twisted有一整套固定的路数,只能照章行事,没有自由发挥的余地。前面把twisted的套路概括成一句话,“一个中心,两个基本点”,现在就从这个“中心”聊起。 

Twisted 官方说,“ Twisted is an event-driven networking framework ”。事实的确如此。从其运行机制上看,event 是 Twisted 运转的引擎,是发生各种动作的启动器,是牵一发而动全身的核心部件。从其架构组成上看,它是紧密围绕event设计的;它的具体应用application,主要是定义、实现各式各样的event,由此完成不同网络协议的连接和输入输出任务,满足用户的实际需求;从其application的文本形式上,可以直接看到,它的应用程序,基本上由一系列event构成。 

由此可见,说它以event为中心,符合实际情况。 

Twisted 对event 的管理机制,可划分为后台和前台两种形式。 

后台的管理,是Twisted 框架的内在机制,自动运行,对程序员透明无须干预,在程序文本中不见其踪迹。 

前台的管理,是Twisted 授权程序员,在程序文本中显式写码来实现。程序员的工作,主要是按照既定的方式,实现 event。我们所关心、所用到的,是这部分东西(API)。 

Twisted 众多的 event,分门别类、层次有序。前台管理中,有两个特别的 object,一个叫 reactor ,另一个叫defered。特别之处,在于它俩起着“事件管理器”的作用。下面,说说它俩。 

一、统领全局的 reactor 

在 Twisted 应用中,reactor 的任务是为程序运行建立必须的全局循环(event loop),所起的作用,相当于 Python 应用中的 MainLoop()。 
reactor 的用法很简单,一般只用两个:reactor.run() 启动全局循环,reactor.stop() 停止全局循环(程序终止)。 
如果程序中没有调用reactor.stop() 的语句,程序将处于死循环,可以按键 Ctrl-C 强制退出。 
下面是一个例子: 

 

  1. from twisted.internet import reactor 
  2. import time 
  3. def printTime( ): 
  4.     print "Current time is", time.strftime("%H:%M:%S") 

  5. def stopReactor( ): 
  6.     print "Stopping reactor" 
  7.     reactor.stop( ) 

  8. reactor.callLater(1, printTime) 
  9. #定时器,1秒钟后调用printTime() 
  10. reactor.callLater(2, printTime) 
  11. reactor.callLater(3, printTime) 
  12. reactor.callLater(5, stopReactor) 
  13. #定时器,5秒钟后调用stopReactor()
  14. print "Running the reactor..." 
  15. reactor.run( ) 
  16. print "Reactor stopped."
二、提升效率的 defferred 


Twisted 官方称,“Twisted is event-based, asynchronous framework ”。这个“异步”功能的代表就是 defferred。 

defferred 的作用类似于“多线程”,负责保障多头连接、多项任务的异步执行。 

当然,defferred “异步”功能的实现,与多线程完全不同,具有以下特点: 

1、defferred 产生的 event,是函数调用返回的对象; 

2、defferred 代表一个连接任务,负责报告任务执行的延迟情况和最终结果; 

3、对defferred 的操作,通过预定的“事件响应器”(event handler)进行。 

有了defferred,即可对任务的执行进行管理控制。防止程序的运行,由于等待某项任务的完成而陷入阻塞停滞,提高整体运行的效率。 

请看下面的例子: 

建议只关注黑体字的语句,它们反映了defferred的用法。涉及的两个class,是Twisted建立网络连接的固定套路,后面会专门说它。 

 

  1. # connectiontest.py 
  2. from twisted.internet import reactor, defer, protocol 

  3. class CallbackAndDisconnectProtocol(protocol.Protocol): 
  4. # Twisted建立网络连接的固定套路 

  5. def connectionMade(self): 
  6. self.factory.deferred.callback("Connected!") 
  7. # “事件响应器”handleSuccess对此事件作出处理 

  8. self.transport.loseConnection( ) 


  9. class ConnectionTestFactory(protocol.ClientFactory): 
  10. # Twisted建立网络连接的固定套路 

  11. protocol = CallbackAndDisconnectProtocol 

  12. def __init__(self): 
  13. self.deferred = defer.Deferred( ) 
  14. # 报告发生了延迟事件,防止程序阻塞在这个任务上 

  15. def clientConnectionFailed(self, connector, reason): 

  16. self.deferred.errback(reason) 
  17. # “事件响应器”handleFailure对此事件作出处理 

  18. def testConnect(host, port): 
  19. testFactory = ConnectionTestFactory() 
  20. reactor.connectTCP(host, port, testFactory) 

  21. return testFactory.deferred 
  22. # 返回连接任务的deferred 

  23. def handleSuccess(result, port): 
  24. # deferred“事件响应器”:连接任务完成的处理 

  25. print "Connected to port %i" % port 
  26. reactor.stop() 


  27. def handleFailure(failure, port): 
  28. # deferred“事件响应器”:连接任务失败的处理 

  29. print "Error connecting to port %i: %s" % (port, failure.getErrorMessage())
  30. reactor.stop( ) 


  31. if __name__ == "__main__": 

  32. import sys 

  33. if not len(sys.argv) == 3: 
  34.  print "Usage: connectiontest.py host port" 
  35. sys.exit(1) 


  36. host = sys.argv[1] 
  37. port = int(sys.argv[2]) 

  38. connecting = testConnect(host, port) 
  39. # 调用函数,返回deferred 

  40. connecting.addCallback(handleSuccess, port) 
  41. # 建立deferred“事件响应器” 

  42. connecting.addErrback(handleFailure, port) 
  43. # 建立deferred“事件响应器” 

  44. reactor.run( )
三、创建 client 的套路 


第二节说到的两个类,是TCP协议客户端的创建套路(方式)。这个套路拆解如下: 

1、定义“工厂”和“协议”两个类: 

(1)“协议”类是 CallbackAndDisconnectProtocol,“工厂”类是 ConnectionTestFactory 

类的名字不重要,但必须正确说明所继承的父类: 

class CallbackAndDisconnectProtocol(protocol.Protocol) 

class ConnectionTestFactory(protocol.ClientFactory) 

(2)“协议”类是“工厂”类实例化的:protocol = CallbackAndDisconnectProtocol; 

(3)只在“工厂”类中有 __init__ 函数,并在其中实例化一个deferred 对象: 

self.deferred = defer.Deferred( ) 

(4)在“工厂”类中,重设父类函数 clientConnectionFailed,通过deferred 引发事件,报告连接失败: 

self.deferred.errback(reason)  

(5)在“协议”类中,重设父类函数 onnectionMade,由对象factory引用“工厂”类中的deferred,经其引发事件,报告连接正常: 

self.factory.deferred.callback("Connected!") 

并由对象transport引发事件,报告连接断开: 

self.transport.loseConnection( ); 

上述“对象”,都是从各自父类继承来的。 


2、在函数testConnect(host, port)中, 

(1)将“工厂”类实例化:testFactory = ConnectionTestFactory( ) 

(2)由全局循环“主持人”reactor建立以testFactory为“主演”的TCP连接: 

reactor.connectTCP(host, port, testFactory) 

(3)返回deferred对象:return testFactory.deferred 


至此,一个以事件驱动为基础、异步执行任务的框架程序搭成了。 

上述三节的内容,据 Twisted 官方说,是“学习曲线最陡”的部分(They represent the steepest part of the 

Twisted learning curve.)。 

我的感受,造成“最陡”的原因,是由于套路新颖独特,初学乍练不易适应。 

1、框架对象众多,一时记不牢; 

2、对象之间的关系比较复杂,一时理不清; 

3、“事件驱动”这种模式,反映在程序文本中,有时见不到明显的函数调用,让人觉得程序的去向不明; 

另外,学习方法很重要。如果以学“语言”的习惯来学框架,遇上问题寻根究底,过分追求“水落石出”;或者,依赖教科书、畸重“理论”,忽视 examples 语句、结构和API文档的分析研究,都不利于翻越这段陡坡。 
 
据我的体验,集中精力地啃嚼主干骨架,不纠緾于细枝末节,这段最陡的上坡路,顶多爬个十天八天的,就能越过去。

四、创建 server 的套路 


网络程序,总得传送数据什么的。本节说说这事儿,一个玩具式的对话服务器。 

咱这儿说事儿,还是老套路,用例程说话。 

我是这样想的:框架这玩艺儿,是让咱比着葫芦画瓢使的,不必太在意葫芦为啥长成那般模样。自己试着例程跑得起来,自然这瓢是画对了,也不用管那模特葫芦的内瓤是啥名堂了。当然,尽量把葫芦内瓤搞清楚更好,画起瓢来心里更有底、更塌实吧。言归正传,画瓢开始。 

 

  1. from twisted.internet import reactor, protocol 

  2. class Talk(protocol.Protocol): 

  3. def dataReceived(self, data): 

  4. print "Client:",data 

  5. if not data=='bye': 
  6. s='' 
  7. while(s==''): 
  8. s=raw_input('Server: ') 
  9. s=s.strip() 
  10. self.transport.write(s) 

  11. def main(): 

  12. factory = protocol.ServerFactory() 
  13. factory.protocol = Talk 
  14. reactor.listenTCP(8000,factory) 
  15. reactor.run() 

  16. if __name__ == '__main__': 
  17. main()

先来说说咱这“瓢”。 

1、服务器的构建启动:从调用函数 main() 开始,生成“工厂”对象factory,指定factory用的通讯“协议”为Talk,指定以端口8000和factory构成服务器,并启动它。 

2、服务器的运行:类Talk中的方法dataReceived,实际上是个event,当收到客户端传来的数据时,它做这几件事儿: 
(1)在显示器上写出:Client:和传来的数据; 
(2)如果传来的数据是字符串“bye”,不作处理。否则,屏显提示“Server:",要求键入一行字符;如果键入的不全是空格,将其传给客户端。 

再来看看“葫芦的内瓤”。 

1、为什么在程序中,没有“工厂”类。在protocol.py中定义的类ServerFactory是个空类,没有“方法”可以重设(注:protocol.py中各类的“方法”,基本上都是“事件”);其父类Factory中虽有方法可供重设,但本程序太简单用不着。 

在类Factory中,只有以下3个方法可以在程序文本中重设: 

(1)buildProtocol(self, addr),用以改变“工厂”类所用Protocal的创建方式; 

(2)startFactory(self),在factory开始监听连接前,仅调用一次。用于连接数据库、打开文件等操作; 
 
(3)stopFactory(self),用于关闭数据库、文件等操作。 

可否在程序文本中显式调用以上3个方法,protocol.py注释只明确说,不允许调用stopFactory。但从逻辑上,它们作为“事件”,应该只能重设不许直接调用。 

2、类Protocol的简单介绍 

它有2个方法用以重设: 

(1)dataReceived(self, data),当收到客户端传来的数据时,执行它。data是不定长字符串; 

(2)connectionLost(self, reason=connectionDone),当连接断开时,执行它;reason的类型是: L{twisted.python.failure.Failure} 

3、类Protocol父类 BaseProtocol 的简单介绍 

BaseProtocol 是各种网络协议的抽象父类。如果实现一个新协议加入Twisted,不管是客户端还是服务器端的协议,都应该以 BaseProtocol作为父类。 

BaseProtocol 的API很简单。在子类 Protocol 中实现了方法 dataReceived(data) ,处理基于事件和异步的输入;输出则由属性对象 'transport' 传送。'transport' 实现的是L{twisted.internet.interfaces.ITransport}。 

定义了二个方法,其中,可重设的是 connectionMade(self),当连接完成时,执行它。可以将其看作”协议“类的初始化方法。 


下面是客户端。它已是脸熟的老朋友了,但没用deferred,其他不用多说了吧。 

 

  1. from twisted.internet import reactor, protocol 

  2. class TalkClient(protocol.Protocol): 

  3. def connectionMade(self): 
  4. s=self.say() 
  5. self.transport.write(s) 

  6. def dataReceived(self, data): 
  7. print "Server:", data 
  8. s=self.say() 
  9. self.transport.write(s) 

  10. if s=='bye': 
  11. self.transport.loseConnection() 

  12. def connectionLost(self, reason): 
  13. print "connection lost" 
  14. from twisted.internet import reactor 
  15. reactor.stop() 

  16. def say(self): 
  17. s='' 
  18. while(s==''): 
  19. s=raw_input('Client: ') 
  20. s=s.strip() 
  21. return s 

  22. class TalkFactory(protocol.ClientFactory): 
  23. protocol = TalkClient 

  24. def clientConnectionFailed(self, connector, reason): 
  25. print "Connection failed - goodbye!" 
  26. reactor.stop() 

  27. def clientConnectionLost(self, connector, reason): 
  28. print "Connection lost - goodbye!" 
  29. reactor.stop() 


  30. def main(): 
  31. = TalkFactory() 
  32. reactor.connectTCP("localhost", 8000, f) 
  33. reactor.run() 

  34. if __name__ == '__main__': 

  35. = TalkFactory() 
  36. reactor.connectTCP("localhost", 8000, f) 
  37. reactor.run()
学习Twisted最好的资料: http://krondo.com/?page_id=1327



原文链接:http://blog.csdn.net/u010415792/article/details/9006531
加载中
返回顶部
顶部