智慧 + 毅力 = 无所不能

正确性、健壮性、可靠性、效率、易用性、可读性、可复用性、兼容性、可移植性...
posts - 891, comments - 26, trackbacks - 0, articles - 0

导航

公告

Twisted网络编程必备(1)注:测试版本Twisted-10.1.0.winxp32-py2.6,对于原代码略有修改

Posted on 2010-11-22 15:39 Bill Yuan 阅读(...) 评论(...) 编辑 收藏

转自:http://www.yybug.com/read-htm-tid-15324.html

为什么使用Twisted?

 

如果你并不准备使用Twisted,你可能有很多异议。为什么使用Twisted而不是其他网络函数库或框架?如下是一些充分的理由:

·基于Python

Twisted是使用Python编写的,强壮的、面向对象的解释性语言。Python使它的爱好者充满热情。使用Python编程是一种乐趣,易于编写、易于阅读、易于运行。因为Python是跨平台的,所以可以运行Twisted程序在Linux、Windows、Unix和MAC等等系统上。

·异步和事件驱动的

同步网络编程函数库留给开发者一个痛苦的抉择:要么允许程序在网络通信时失去响应,或者引入非常复杂的线程。Twisted是基于事件的,异步网络通信框架允许编写的程序在处理事件时保持相应,却不需要使用线程。

·多功能

Twisted包括大量的功能。Email、WEB、news、chat、DNS、SSH、Telnet、RPC、数据库存取或者更多。所有的都为你准备好了。

·灵活

Twisted提供了高层类允许快速开始。而且并没有感到受限。如果需要高级功能,或者需要自定义网络协议,也是可以的。你可以实现自己的网络协议,控制每一个字节。

·开放源代码

Twisted是免费的。它包含源代码,按照函数库协议发行。并且欢迎在你的程序中使用Twisted,不必支付任何费用和担心法律问题。如果希望知道一个对象的运行原理也可以直接看源码。如果你开发出了自己的新功能和扩展,欢迎与我们共享。

·社区支持

Twisted有一个活跃的社区包含开发者和用户。如果你发现了问题,也可以在邮件列表上找到很多开发者来帮助你。查看第一章的寻找FAQ一节。或者进入#twisted这个IRC频道,来与开发者进行在线交流。

·易于整合的平台

一个Twisted程序可以在多种服务之间共享数据,可以整合很多工作。比如可以编写SMTP到XMLRPC的代理,一个通过SSH来更新网站的服务,或者一个包含NNTP接口的WEB讨论组。如果需要在平台之间交换数据,Twisted是个很好的选择。

 

启动Twisted的事件循环

Twisted是事件驱动的框架。这意味着不再使用特定序列来处理程序逻辑,而是通过被动调用一些函数来实现。例如,GUI程序里面使用的"button pressed"事件。设计者不需要事先确定事件何时发生,只需要编写事件的响应函数即可。这就是所谓的事件处理器(event handler)。

 

每个事件驱动的框架都包含了一个特殊的函数叫做事件循环(event loop)。每次启动,事件循环都会立即运行。这个函数运行时等待事件的发生。当事件发生时,事件循环函数会自动触发相关的事件处理函数。

使用事件循环需要改变一些顺序编程的习惯。一旦开始了事件循环,你将无法控制程序的执行顺序,只会自动响应事件。所以,你需要为程序设计事件处理器。也就是事件发生时的响应。

在Twisted中,有一种特殊的对象用于实现事件循环。这个对象叫做reactor。可以把反应器(reactor)想象为Twisted程序的中枢神经。除了分发事件循环之外,反应器还做很多重要的工作:定时任务、线程、建立网络连接、监听连接。为了让反应器可以正常工作,需要启动事件循环

 

启动反应器

 

启动反应器是很简单的,从twisted.internet模块导入reactor对象。然后调用reactor.run()来启动反应器的事件循环。下例展示了代码:

from twisted.internet.selectreactor import SelectReactor
print 'Running the reactor ...'
reactor = SelectReactor()
reactor.run()
print 'Reactor stopped.'

 

反应器将会一直运行,直到接到停止的通知,可以按下Ctrl+C来退出事件循环,并终止程序:

$ python runreactor.py
Running the reactor ...
<ctrl-c>
^CReactor stopped.

 

这是一个简洁的例子。尽管反应器运行着,但是没有做任何事情。下面的例子提供了更多有趣的事情,介绍了反应器的callLater方法。reactor.callLater方法用于设置定时事件。这个方法在用于定时在未来执行。比如一个函数已经是一个事件处理器了,这种事件会在一定时间之后启动。下面的例子,函数调用了一定时间之后被调用:

 

代码
from twisted.internet.selectreactor import SelectReactor
import time
reactor = SelectReactor()
def printTime():
print 'Current time is',time.strftime("%H:%M:%S")
def stopReactor():
print "Stopping reactor"
reactor.stop()
reactor.callLater(
1,printTime)
reactor.callLater(
2,printTime)
reactor.callLater(
3,printTime)
reactor.callLater(
4,printTime)
reactor.callLater(
5,stopReactor)
print 'Running the reactor ...'
reactor.run()
print 'Reactor stopped.'

 

运行这个程序,可以看到如下结果:

$ python calllater.py

Running the reactor ...

Current time is 10:33:44

Current time is 10:33:45

Current time is 10:33:46

Current time is 10:33:47

Stopping reactor

Reactor stopped.

 

它是如何工作的

 

第一个例子简介了导入与执行reactor.run()来启动事件循环。反应器将会一直保持执行直到按下Ctrl-C,尽管这一段时间什么都不做。在这时,反应器停止了,程序执行到代码的最后一行,并打印出反应器已经停止的消息。

第二个例子使用reactor.callLater函数定时执行函数。reactor.callLater函数包含两个必须参数,等待的秒数,和需要调用的函数。在设置了定时函数调用之后,控制就回到了反应器的事件循环reactor.run()中了。

Tip:你可以传递附加的参数和键值对到reactor.callLater中,这些用于调用指定的函数。例如,reactor.callLater(1,func,True,x="Hello"),将会最终调用func(True,x="Hello"),在一秒钟之后。

第一个定时函数是printTime(),简单显示了当前时间。第五个定时函数是stopReactor(),其中调用了reactor.stop(),导致了反应器退出了事件循环。这也是为什么不需要按下Ctrl-C而自动退出了事件循环的原因。

Tip:在这种情况下仍然可以按下Ctrl-C来手动停止事件循环。

注意事件的发生顺序,反应器按照给定的时间来调用指定的函数。一旦反应器开始运行,反应器就会控制事件循环,并在指定时间调用函数。反应器在被告知停止之前会一直运行,直到reactor.stop()调用。一旦反应器停止了,程序将继续处理最后一行,显示反应器停止的消息。

Tip:在实际应用中,reactor.callLater是常用于超时处理和定时事件。可以设置函数按照指定的时间间隔来执行关闭非活动连接或者保存内存数据到硬盘。

 

建立(establishing)一个TCP连接

 

所有的网络应用程序都必须做一个简单的步骤,开启一个连接。接着可以发送邮件、传递文件、看电影等等,但是在所有这些之前,必须在电脑之间建立一个连接。本节讲解了如何使用reactor开启TCP连接。

 

调用reactor.connectTCP()方法打开一个TCP连接,传递一个ClientFactory对象作为第三个参数。ClientFactory对象等待连接被建立,然后创建一个Protocol对象来管理连接中的数据流。下面的例子展示了如何在电脑和Internet之间建立一个连接(www.google.com):

代码
from twisted.internet.selectreactor import SelectReactor
from twisted.internet.protocol import Protocol,ClientFactory
reactor
= SelectReactor()
protocol
= Protocol()
class QuickDisconnectedProtocol(Protocol):
def connectionMade(self):
print "Connected to %s."%self.transport.getPeer().host
self.transport.loseConnection()
class BasicClientFactory(ClientFactory):
protocol
=QuickDisconnectedProtocol
def clientConnectionLost(self,connector,reason):
print 'Lost connection: %s'%reason.getErrorMessage()
reactor.stop()
def clientConnectionFailed(self,connector,reason):
print 'Connection failed: %s'%reason.getErrorMessage()
reactor.stop()
reactor.connectTCP(
'www.google.com',80,BasicClientFactory())
reactor.run()

 

 

当运行这个例子的时候,可以看到如下输出:

$ python connection.py

Connected to 66.249.89.104.
Lost connection: Connection was closed cleanly.

除非你的电脑没有在线。在这种情况下,会看到如下错误信息:

$ python connection.py

Connection failed: DNS lookup failed: address 'www.google.com' not found.

 

它是如何工作的

 

这里有两个主要的类用于作为客户端工作,ClientFactory和Protocol。这些类被设计成处理连接中所有可能运到的事件:成功建立连接、连接失败、连接断开、数据传送等等。

ClientFactory和Protocol有严格的不同。ClientFactory的工作是管理连接事件并且创建Protocol对象处理每一个成功的连接。一旦连接建立,Protocol对象就接管下面的工作了,包括收发数据和决定是否关闭连接。

Tip:名字"Factory"在ClientFactory是来自于Protocol的请求,响应每一个成功的连接。

例子定义了自定义的Protocol叫做QuickDisconnectProtocol,继承自protocol.Protocol。它重载了一个方法connectMade。这个方法连接成功时运行,在reactor刚刚成功建立了连接,然后ClientFactory创建了QuickDisconnectProtocol的实例时。有如他的名字,QuickDisconnectProtocol对象在打印信息之后就马上关闭了。

Tip:Protocol对象有一个属性叫做transport,包含了当前活动连接对象。

BasicClientFactory是继承自protocol.ClientFactory的类。它首先设置了类变量protocol为QuickDisconnectProtocol。这个类的实例被创建用于管理成功的连接。

BasicClientFactory重载了ClientFactory的两个方法,clientConnectionLost和clientConnectionFailed。这两个方法是事件处理器。clientConnectionFailed在反应器无法建立连接时被调用。clientConnectionLost在建立的连接被关闭或断开时调用。

告知反应器建立TCP连接,按照例子2-3的方法是调用reactor.connectTCP:

reactor.connectTCP('www.google.com',80,BasicClientFactory())

这一行告知反应器建立一个TCP连接到服务器www.google.com的80端口,通过BasicClientFactory来管理连接。