Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5119637
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2011-08-30 16:12:09

Twisted网络编程必备(一)
 
 
0.1 为什么使用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是个很好的选择。:if expand("%") == ""|browse confirm w|else|confirm w|endif
0.2 本书包含的内容
 
本书并不是讲解Twisted框架中的每一个类,而是关注于更加真实的例子。本书也会帮助你理解Twisted中使用的关键技术和设计模式。如下是主要内容列表:
·安装Twisted
第一章讲解了下载和安装还有平台相关扩展库。
·使用TCP连接
第二章告诉你如何使用Twisted建立TCP连接,包括服务器和客户端。
·WEB客户端和服务器
第三、四章讲解了如何使用Twisted工作于WEB之上。第三章模拟WEB客户端。第四章编写了一个示例HTTP服务器,实现了基本的等级管理和响应功能。
 
·WEB服务和RPC
Twisted内置支持了多种WEB服务和远程调用方法。第五章讲解了如何在REST架构下建立应用。也讲解了如何编写XMLRPC和SOAP客户端和服务器,和如何将Python对象转换为网络连接。
·认证
管理用户和认证权限是很多程序的核心功能之一。第六章讨论了实现方法,并提供了在多种不同应用中具有很好移植性的框架。
·电子邮件客户端和服务器
第七、八章给出了电子邮件支持。第七章的例子展示了如何编写SMTP、POP3、IMAP客户端来收发电子邮件。第八章展示了如何构建SMTP、POP3、IMAP服务器。
·Usenet新闻组
第九章讨论了使用NNTP协议的Usenet客户端和服务器。并展示了如何阅读和发送消息,如何运行NNTP服务器,如何使用NNTP作为其他方式的接口,如RSS等。
·SSH客户端和服务器
第十章讲解了SSH支持。例举了编写SSH服务器的程序,并提供了远程可用的Python解释器。这一章也展示了如何编写SSH客户端来执行远程命令。
·运行和管理Twisted程序
第十一章展示了如何使用Twisted工具运行和管理应用程序,比如作为后台精灵线程,使用setuid和chroot限制权限,和写入日志文件。
1.0 快速开始
 
在你使用Twisted进行开发之前,你应该下载并安装。这一章讲解在各种操作系统下的安装过程。也包括将Twisted实用工具加入到路径和熟悉文档。其他问题可以到Twisted社区提问。
1.1 安装Twisted
 
首先应该下载Twisted用于安装。可以到下载。为了安装附加属性,还可以安装另外两个可选软件包。
 
Twisted网络编程必备(二)
 
 
1.1.1 下面该如何做?
到下载最新版本的Twisted。然后安装PyOpenSSL(一个Python开源OpenSSL库),这个软件包用于给Twisted提供加密传输支持(SSL)。最后,安装PyCrypto,一个包含了Python安全算法的包,用于提供SSH支持。这些软件包的下载地址在后面各个节中指定。
为了使用Twisted并不一定要安装PyOpenSSL和PyCrypto。但是不安装这些,将无法使用Twisted的SSL和SSH功能,其他功能还是可用的。
 
1.1.1.1 Windows
到下载Windows版Python2.4安装包。这个二进制包包含了Twisted的核心功能,包括大量的扩展包和协议支持。如果需要安装更多的例子,可以到下载"Twisted Dependencies for Windows"。这个安装包已经包含了PyOpenSSL和PyCrypto。
这些页面有可能已经移动到其他页面中了,并且增加了一些新功能。如果找不到这些连接,可以尝试从访问。
如果一次下载了这三个软件包,运行Twisted安装程序。这是一个小的安装包,可以自动检测Python安装路径,下一步运行PyOpenSSL和PyCrypto安装包。这些都是很简单的,也只需要很少的几秒钟。
当编译Twisted模块时,可能需要一点时间,进度条会显示当前状态。解包也会耗费几分钟。
证实是否安装成功的方法是导入twisted包。
$ python
... ...
>>> import twisted
>>>
如果"import twisted"执行成功而没有错误,则已经安装好了Twisted。
 
1.1.1.2 MAC OS X, Linux, 与BSD
 
很多Linux和BSD的发行版,包括Debian、Ubuntu、Gentoo、Red Hat、FreeBSD等等已经预先安装了Twisted了,而且PyOpenSSL和PyCrypto也是可用的。可以检查你的OS是否被Twisted下载页所提及,或者搜索已经安装包历史可以看到。如果已经安装了,最好确保版本是Twisted 2.0或更高,而不是1.3的release版本。
如果没有预安装Twisted,则需要下载源码来安装。
1.2 从源码安装
 
如果你的操作系统上没有预安装Twisted,则需要从源代码安装。不必担心,从源码安装对于Python也很简单。
 
1.2.1 下面如何做
 
首先,下载完整的Twisted源码包。附带的包可以让Twisted功能更多。运行本书的大多数例子都需要完整的安装。一旦下载了软件包,就解压到工作目录。
$ tar -xjvf ~/downloads/TwistedSumo-2005-03-22.tar.bz2
下一步进入解压后的目录。Twisted依赖于zope.interface包,而这个包已经附带在了Twisted Sumo发行包中了。解压ZopeInterface压缩包:
$ tar -xzvf ZopeInterface-3.0.1.tgz
进入ZopeInterface目录,运行命令"python setup.py install"。这个命令将会构建和安装zope.interface包到python安装目录的lib/site-packages/twisted目录。这里是需要Administrator/root权限的。所以使用su或sudo来增加权限级别。
$ cd ZopeInterface-3.0.1
$ python setup.py install
一旦zope.interface安装完成,就可以安装Twisted了。在Twisted目录下,运行"python setup.py install"。这个命令将会编译Twisted的C模块,并安装Twisted:
$ cd TwistedSumo-2005-03-22
$ python setup.py install
如果你已经安装了多个版本的Python,确保Twisted安装在你正在运行的python命令对应的那个版本上。书的例子可以运行在Python 2.3或更高版本。检查你的Python版本通过"python -V"。
祝贺,你已经成功安装了Twisted。现在测试一下是否安装成功,运行Python提示符:
$ python
>>> import twisted
>>>
如果"import twisted"语句执行成功则Twisted安装成功了。
下一步,下载最新版本的PyOpenSSL()。PyOpenSSL运行在OpenSSL库之上,所以你需要确保OpenSSL已经安装了。Mac OS X已经默认安装了OpenSSL,并包含了头文件,Linux和BSD的大部分发行版也默认包含了这些。
如果你正巧倒霉安装了一个默认没有OpenSSL库的发行版,则需要下载并安装源码包,从这里()。
为了安装PyOpenSSL,需要执行如下步骤。首先解压下载文件:
$ tar -zxvf PyOpenSSL-0.6.tar.gz
然后,选择PyOpenSSL库并运行"python setup.py install",使用root用户。
$ cd PyOpenSSL-0.6
$ python setup.py install
当安装完成了,确保OpenSSL包是可用的,可以用导入来测试:
$ python
>>> import OpenSSL
>>> import twisted.internet.ssl
>>> twisted.internet.ssl.SSL
'....'>
如果没有看到任何错误,则已经为Twisted成功的安装了SSL支持。
最后一个需要安装的包是PyCrypto。PyCrypto是Python的加密工具集,是A.M.Kuchling开发的一个包含了众多加密算法实现的包。Twisted使用PyCrypto来支持SSH连接。
从()来下载,并解压:
$ tar -xzvf pycrypto-2.0.tar.gz
运行很熟悉的"python setup.py install"来安装,在PyCrypto的目录中。
$ cd pycrypto-2.0
$ python setup.py install
确保这个包是否安装成功可以用导入测试。你可以测试twisted.conch.ssh.transport模块可以使用PyCrypto的RSA实现:
$ python
>>> import Crypto
>>> import twisted.conch.ssh.transport
>>> twisted.conch.ssh.transport.RSA
'....'>
如果是如上的显示,则你已经成功的安装了PyCrypto。在这个是时候,你就可以利用Twisted,并且使用SSL和SSH功能了
 
Twisted网络编程必备(三)
 
 
1.3 添加Twisted实用工具到PATH路径参数
 
Twisted包含了一些实用工具脚本。为了方便使用,应该将他们添加到PATH可以找到的地方。
 
1.3.1 下面如何做
 
一般来说,所有添加到PATH的路径都是可以被OS找到的。按照如下步骤来做。
 
1.3.1.1 Windows
 
Twisted的使用工具将会安装到Python的scripts目录(典型为c:\Python23\scripts)。Twisted包含了一些菜单设置程序,可以通过Programs->Twisted->Twisted Command Prompt。来进入。使用这个菜单入口可以使用提示符模式来运行实用工具,而且已经自动设置好了PATH路径了。
 
1.3.1.2 Linux
 
Twisted的使用工具将会安装到python二进制目录的相同文件夹(可能是/usr/bin或/usr/local/bin),所以需要将这个路径添加到PATH路径。
 
1.3.1.3 Mac OS X
 
如果正在使用Mac OS X 2.3 (jaguar)或更新版本,Twisted已经默认安装在了(/System/Library/Frameworks/Python.framework/Versions/Current/bin。将这个路径添加到PATH变量。
$ set PATH=$PATH:/System/Library/Frameworks/Python.framework/Versions/Current/bin
1.4 使用Twisted的文档
 
Twisted包含了一些不同类型的文档,包括扩展API文档、HOWTO文档、快速入门和例子程序。十分推荐尽快熟悉这些文档,并在开发过程中提供帮助。
 
1.4.1 下面如何做
 
Twisted的文档是在网站上在线观看的。一个完整的API文档在(http://twistedmatrix.com/documents/current/api)。为了编程,你需要多次使用这个文档。这份API文档是由代码自动生成的,所用的工具是Twisted的一个组件叫做lore。
Twisted是由一些子工程构成的,每个子工程包含自己的文档。例如,核心模块文档在(http://twistedmatrix.com/projects/core/documentation),而关于WEB模块的文档在(http://twistedmatrix.com/projects/web/documentation)。在主页有进入每一个子工程的链接。
每个子工程都包含以下类型的文档:
·HOWTOs
描述特殊功能和如何使用这些功能。并不覆盖所有的只是,但可以提供对特定任务的快速入门。包含在HOWTOs中的快速入门(tutorial)叫做"Twisted From Scratch",讲解如何开发一个应用程序,并扩展的讲解一些高级内容。
·Examples
简短而特别的Twisted代码,有点像HOWTOs,但是可以在使用中对特定功能提供有效的帮助。
·Manual pages
这是HTML版本的manpages,描述了如何使用Twisted实用工具。
 
1.4.2 关于
 
如果想要浏览文档而不通过浏览器,那么可以直接使用pydoc工具来浏览。pydoc并不能展示Twisted文档中所有的标志,当仍然很有用。Figure 1-1展示了pydoc展示twisted.web.http的信息。
当然,真的想要理解Twisted的工作,可以阅读Twisted的源代码。因为有些模块为了性能而使用了C语言来写,除此之外可以到site-packages/twisted目录中查看。或者打开appropriate.py文件。
1.5 为自己的问题寻找答案
 
大多数问题可以从文档中自己找到答案,或者可以从社区得到答案。
 
1.5.1 下面如何做
 
有一些有益的社区资源可以提供帮助。首先是邮件列表。"twisted-python"列表是提供一般讨论的。"twisted-web"列表提供了WEB应用程序的讨论。如果问题提错了地方,会被自动转到另外一个讨论组。可以在如下地址申请讨论组:
第二,可以在#twisted和#twisted.web这两个IRC频道讨论问题()。这些频道是一直开放且有趣的,问题可以很及时的得到回答。如果不要求及时得到答复,最好还是到邮件列表讨论,这种方式提供更加广泛的关注,并且可以让更多的人分享。
最后的可用资源是Planet Twisted社区。地址在,这个站点的weblog提供了大量的知识。这是一种很好的获取知识的方式,这也提供了RSS订阅方式。
2. 建立简单的客户端和服务器
 
使用Twisted进行开发,需要学习如何使用新的类和对象。这些类是Twisted的核心,你将会在你的应用中使用这些类。它们提供了平滑的学习曲线,理解如何使用他们,将会使得使用Twisted进行开发更加简便。
这一章展示了如何编写简单的客户端和服务器。并介绍Twisted简单的类和工作流程,并展示如何使用它们。
 
Twisted网络编程必备(四)
 
 
2.1 启动Twisted的事件循环
 
Twisted是事件驱动的框架。这意味着不再使用特定序列来处理程序逻辑,而是通过被动调用一些函数来实现。例如,GUI程序里面使用的"button pressed"事件。设计者不需要事先确定事件何时发生,只需要编写事件的响应函数即可。这就是所谓的事件处理器(event handler)。
每个事件驱动的框架都包含了一个特殊的函数叫做事件循环(event loop)。每次启动,事件循环都会立即运行。这个函数运行时等待事件的发生。当事件发生时,事件循环函数会自动触发相关的事件处理函数。
使用事件循环需要改变一些顺序编程的习惯。一旦开始了事件循环,你将无法控制程序的执行顺序,只会自动响应事件。所以,你需要为程序设计事件处理器。也就是事件发生时的响应。
在Twisted中,有一种特殊的对象用于实现事件循环。这个对象叫做reactor。可以把反应器(reactor)想象为Twisted程序的中枢神经。除了分发事件循环之外,反应器还做很多重要的工作:定时任务、线程、建立网络连接、监听连接。为了让反应器可以正常工作,需要启动事件循环。
 
2.1.1 下面如何做
 
启动反应器是很简单的,从twisted.internet模块导入reactor对象。然后调用reactor.run()来启动反应器的事件循环。下例展示了代码:
from twisted.internet import reactor
print 'Running the reactor ...'
reactor.run()
print 'Reactor stopped.'
反应器将会一直运行,直到接到停止的通知,可以按下Ctrl+C来退出事件循环,并终止程序:
$ python runreactor.py
Running the reactor ...
^CReactor stopped.
这是一个简洁的例子。尽管反应器运行着,但是没有做任何事情。下面的例子提供了更多有趣的事情,介绍了反应器的callLater方法。reactor.callLater方法用于设置定时事件。这个方法在用于定时在未来执行。比如一个函数已经是一个事件处理器了,这种事件会在一定时间之后启动。下面的例子,函数调用了一定时间之后被调用:
from twisted.internet import reactor
import time
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.
 
2.1.2 它是如何工作的
 
例子2-1简介了导入与执行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是常用于超时处理和定时事件。可以设置函数按照指定的时间间隔来执行关闭非活动连接或者保存内存数据到硬盘。
 
2.1.3 建立(establishing)一个TCP连接
 
所有的网络应用程序都必须做一个简单的步骤,开启一个连接。接着可以发送邮件、传递文件、看电影等等,但是在所有这些之前,必须在电脑之间建立一个连接。本节讲解了如何使用reactor开启TCP连接。
 
2.1.4 下面如何做
 
调用reactor.connectTCP()方法打开一个TCP连接,传递一个ClientFactory对象作为第三个参数。ClientFactory对象等待连接被建立,然后创建一个Protocol对象来管理连接中的数据流。下面的例子2-3展示了如何在电脑和Internet之间建立一个连接():
from twisted.internet import reactor,protocol
class QuickDisconnectedProtocol(protocol.Protocol):
    def connectionMade(self):
        print "Connected to %s."%self.transport.getPeer().host
        self.transport.loseConnection()
class BasicClientFactory(protocol.ClientFactory):
    protocol=QuickDisconnectProtocol
    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('',80,BasicClientFactory())
reactor.run()
当运行这个例子的时候,可以看到如下输出:
$ python connection.py
Connected to
Lost connection: Connection was closed cleanly.
除非你的电脑没有在线。在这种情况下,会看到如下错误信息:
$ python connection.py
Connection failed: DNS lookup failed: address '' not found.
 
2.1.5 它是如何工作的
 
这里有两个主要的类用于作为客户端工作,ClientFactory和Protocol。这些类被设计成处理连接中所有可能运到的事件:成功建立连接、连接失败、连接断开、数据传送等等。
ClientFactory和Protocol有严格的不同。ClientFactory的工作是管理连接事件,并且创建Protocol对象处理每一个成功的连接。一旦连接建立,Protocol对象就接管下面的工作了,包括收发数据和决定是否关闭连接。
Tip:名字"Factory"在ClientFactory是来自于Protocol的请求,响应每一个成功的连接。
例子2-3定义了自定义的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('',80,BasicClientFactory())
这一行告知反应器建立一个TCP连接到服务器的80端口,通过BasicClientFactory来管理连接。
 

Twisted网络编程必备(五)
 
 
2.2 使用非同步的方式工作的结果
 
除了反应器reactor之外,Deferred可能是最有用的Twisted对象。你可能在Twisted程序中多次用到Deferred,所有有必要了解它是如何工作的。Deferred可能在开始的时候引起困惑,但是它的目的是简单的:保持对非同步活动的跟踪,并且获得活动结束时的结果。
Deferred可以按照这种方式说明:可能你在饭店中遇到过这个问题,如果你在等待自己喜欢的桌子时,在一旁哼哼小曲。带个寻呼机是个好主意,它可以让你在等待的时候不至于孤零零的站在那里而感到无聊。你可以在这段时间出去走走,到隔壁买点东西。当桌子可用时,寻呼机响了,这时你就可以回到饭店去你的位置了。
一个Deferred类似于这个寻呼机。它提供了让程序查找非同步任务完成的一种方式,而在这时还可以做其他事情。当函数返回一个Deferred对象时,说明获得结果之前还需要一定时间。为了在任务完成时获得结果,可以为Deferred指定一个事件处理器。
 
2.2.1 下面如何做?
 
当编写一个启动非同步操作的函数时,返回一个Deferred对象。当操作完成时,调用Deferred的callback方法来返回值。如果操作失败,调用Deferred.errback函数来跑出异常。例子2-4展示了程序使用Deferred使用非同步操作的连接例子,连接一个服务器的端口。
当调用一个可以返回Deferred的函数时,使用Deferred.addCallback方法指定返回结果时调用的函数。使用Deferred.addErrback方法指定执行发生异常时调用的函数。
from twisted.internet import reactor,defer,protocol
class CallbackAndDisconnectProtocol(protocol.Protocol):
    def connectionMade(self):
        self.factory.deferred.callback("Connected!")
        self.transport.loasConnection()
class ConnectionTestFactory(protocol.ClientFactory):
    protocol=CallbackAndDisconnectProtocol
    def __init__(self):
        self.deferred=defer.Deferred()
    def clientConnectionFailed(self,connector,reason):
        self.deferred.errback(reason)
def testConnect(host,port):
    testFactory=ConnectionTestFactory()
    reactor.connectTCP(host,port,testFactory)
    return testFactory.deferred
def handleSuccess(result,port):
    print "Connect to port %i"%port
    reactor.stop()
def handleFailure(failure,port):
    print "Error connecting to port %i: %s"%(
        port,failure.getErrorMessage())
    reactor.stop()
if __name__=="__main__":
    import sys
    if not len(sys.argv)==3:
        print "Usage: connectiontest.py host port"
        sys.exit(1)
    host=sys.argv[1]
    port=int(sys.argv[2])
    connecting=testConnect(host,port)
    connecting.addCallback(handleSuccess,port)
    connecting.addErrback(handleFailure,port)
    reactor.run()
运行这个脚本并加上服务器名和端口这两个参数会得到如下输出:
$ python connectingtest.py oreilly.com 80
Connecting to port 80.
或者,如果连接的端口是关闭的:
$ python connectiontest.py oreilly.com 81
Error connecting to port 81: Connection was refused by other side: 22: Invalid argument.
或者连接的服务器并不存在:
$ python connectiongtest.py fakesite 80
Error connecting to port 80: DNS lookup failed: address 'fakesite' not found.
 
2.2.2 它是如何工作的?
 
类ConnectionTestFactory是ClientFactory的子类,并且含有Deferred对象作为属性。当连接完成时,CallbackAndDisconnectProtocol的connectionMade方法会被调用。connectionMade会调用self.factory.deferred.callbakc加上任意值标识调用成功。如果连接失败,ConnectionTestFactory的clientConnectionFailed方法会被调用。第二个参数clientConnectionFailed的reason是twisted.python.failure.Failure对象,封装了异常,并描述了异常发生的原因。clientConnectionFailed传递了Failure对象到self.deferred.errback来标识操作失败。
testConnecn函数需要两个参数host和port。用于创建叫做testFactory的ConnectionTestFactory的类实例,并且传递给reactor.connectTCP参数host和port。当连接成功时返回testFactory.deferred属性,就是用于跟踪的Deferred对象。
例子2-4夜展示了两个事件处理器函数:handleSuccess和handleFailure。当从命令行运行时,它们需要host和port两个参数来调用testConnect,指定了结果Deferred到变量connecting。此时可以使用connecting.addCallback和connecting.addErrback来设置事件处理器函数。在每一种情况下,传递port作为附加参数。因为扩展的参数或者关键字参数将会由addCallback或addErrback传递给事件处理器,这将会在调用handleSuccess和handleFailure时得到端口参数作为第二个参数。
Tip:当函数返回Deferred时,可以确定事件调用了Deferred的callback或者errback方法。否则代码将永远等待结果。
在调用了testConnect和设置事件处理器之后,控制劝将会交给reactor.run()。依赖于testConnect的执行成功与否,将会调用handleSuccess或者handleFailure,打印西那个关的信息并停止反应器。
 
2.2.3 关于
 
是否需要保持一串的Deferred呢?有时确实需要同时保持多个非同步任务,且并非同时完成。例如,可能需要建立一个端口扫描器运行testConnect函数来对应一个范围的端口。为了实现这个,使用DeferredList对象,作为例子2-5,来挂历众多的Deferred。
from twisted.internet import reactor,defer
from connectiontester import testConnect
def handleAllResules(results,ports):
    for port,resultInfo in zip(ports,results):
        success,result=resultInfo
        if success:
            print 'Connected to port %i' % port
    reactor.stop()
import sys
host=sys.argv[1]
ports=range(1,201)
testers=[testConnect(host,port) for port in ports]
defer.DeferredList(testers,consumeErrors=True).addCallback(handleAllResults,ports)
reactor.run()
运行portscan.py脚本并传递host参数。将会自动扫描1-200端口并报告结果。
$ python portscan.py localhost
Connected to port 22
Connected to port 23
Connected to port 25
... ...
例子2-5使用了Python的列表(list)的形式来创建一个列表存储由testConnect()返回的Deferred对象的列表。每一个testConnect()使用host参数,并测试1-200端口。
testers=[testConnect(host,port) for port in ports]
例子2-5使用列表方式包装了Deferred对象成为DeferredList对象。DeferredList将会跟踪所有的Deferred对象的结果并传递作为首参数。当它们都完成了的时候,将会回调按照(success,result)的格式。在Deferred完成时,第一个回传值是TRue和第二个参数是Deferred传回结果。如果执行失败,则第一个参数是False且第二个参数是Failure对象包装的异常。consumeErrors键设置为True时告知DeferredList完成了吸收Deferred的所有错误。否则将会看到弹出的错误信息。
当DeferredList完成时,results将会被传给handleAllResults函数,列表中的每个元素都会被扫描。handleAllResults使用了zip函数来匹配每个结果的端口。对每一个端口如果有成功的连接则打印信息。最终反应器会结束并结束程序。
 
Twisted网络编程必备(六)
 
 
2.3 发送和接收数据
 
一旦TCP连接被建立,就可以用于通讯。程序可以发送数据到另一台计算机,或者接收数据。
 
2.3.1 下面如何做?
 
使用Protocol的子类可以发送和接收数据。重载dataReceived方法用于控制数据的接收,仅在接收到数据时才被调用。使用self.transport.write来发送数据。
例子2-6包括了DataForwardingProtocol的类,可以将接收到的数据写入self.output。这个实现创建了简单的应用程序,类似于netcat,将收到的数据传递到标准输出,用于打印接收的数据到标准输出。
from twisted.internet import stdio,reactor,protocol
from twisted.protocols import basic
import re
class DataForwardingProtocol(protocol.Protocol):
    def __init__(self):
        self.output=None
        self.normalizeNewLines=False
    def dataReceived(self,data):
        if self.normalizeNewLines:
            data=re.sub(r"(\r\n|\n)","\r\n",data)
        if self.output:
            self.output.write(data)
class StdioProxyProtocol(DataForwardingProtocol):
    def connectionMade(self):
        inputForwarder=DataForwardingProtocol()
        inputForwarder.output=self.transport
        inputForwarer.normalizeNewLines=True
        stdioWarpper=stdio.StandardIO(inputForwarder)
        self.output=stdioWarpper
        print "Connected to server. Press Ctrl-C to close connection."
class StdioProxyFactory(protocol.ClientFactory):
    protocol=StdioProxyProtocol
    def clientConnectionLost(self,transport,reason):
        reactor.stop()
    def clientConnectionFailed(self,transport,reason):
        print reason.getErrorMessage()
        reactor.stop()
if __name__=='__main__':
    import sys
    if not len(sys.argv)==3:
        print "Usage: %s host port "__file__
        sys.exit(1)
reactor.connectTCP(sys.argv[1],int(sys.argv[2]),StdioProxyFactory())
reactor.run()
运行dataforward.py并传入host和port两个参数。一旦连接就可以将所有来自服务器的消息送回服务器,所有接收的数据也同时显示在屏幕上。例如可以手动连接一个HTTP服务器,并发送HTTP请求到oreilly.com:
$ python dataforward.py oreilly.com 80
Connected to server.  Press Ctrl-C to close connection.
HEAD / HTTP/1.0
Host: oreilly.com
                           <--空行
HTTP/1.1 200 OK
......
 
2.3.2 它们是如何工作的?
 
例子2-6开始于定义类DataForwardingProtocol。这个协议用于接受数据并存入self.output,这个属性可以用write方法访问,如同self.output.write。DataForwardingOutputProtocol包含一个叫做normalizeNewLines的属性。如果这个属性设置为True,将会由Unix风格的\n换行改变为\r\n这种常见的网络换行方式。
作为DataForwardingProtocol的子类StdioProxyProtocol类具体负责工作。一旦连接被建立,将会创建一个叫做inputForwarder的DataForwardingProtocol实例,并设置输出为self.transport。然后包装twisted.internet.stdio.StandardIO的实例inputForwarder,以标准IO的方式代替网络连接。这一步对所有的通过StdioProxyProtocol方式的网络连接都有效。最终设置StdioProxyProtocol的output属性到值stdioWarpper,所以数据的接收工作定义到了标准输出。
协议定义之后,很容易定义StdioProxyFactory,将其protocol属性设置为StdioProxyProtocol,并且处理反应器的停止和连接、失败工作。调用reactor.connectTCP连接,然后依靠reactor.run()来控制事件循环。
2.4 接受客户端的连接
 
前面的实验都是讲解如何作为客户端进行连接。Twisted也同样适合于编写网络服务器,用于等待客户端连接。下面的实验将会展示如何编写Twisted服务器来接受客户端连接。
 
2.4.1 下面如何做?
 
创建一个Protocol对象来定义服务器行为。创建一个ServerFactory对象并引用Protocol,并传递给reactor.listenTCP。例子2-7展示了简单的echo服务器,简单的返回客户端发来的信息。
from twisted.internet import reactor,protocol
from twisted.protocols import baisc
class EchoProtocol(basic.LineReceiver):
    def lineReceived(self,line):
        if line=='quit':
            self.sendLine("Goodbye.")
            self.transport.loseConnection()
        else:
            self.sendLine("You said: "+line)
class EchoServerFactory(protocol.ServerFactory):
    protocol=EchoProtocol
if __name__=="__main__":
    port=5001
    reactor.listenTCP(port,EchoServerFactory())
    reactor.run()
当这个例子运行时,将会在5001端口监听,并报告已经建立的连接。
$python echoserver.py
Server running, Press Ctrl-C to stop.
Connection from  127.0.0.1
Connection from  127.0.0.1
在另一个终端,使用netcat、telnet或者dataforward.py(例子2-6)来连接服务器。将会返回键入的例子。输入quit来关闭连接。
$ python dataforward.py localhost 5001
Connected to server.  Press Ctrl-C to close connection.
hello
You said: hello
twisted is fun
You said: twisted is fun
quit
Goodbye.
 
2.4.2 它们是如何工作的?
Twisted服务器使用相同的Protocol类作为客户端。为了复用,EchoProtocol继承了twisted.protocols.basic.LineReceiver,作为Protocol.LineReceiver的轻量级实现,可以自动按照获得的行来产生处理。当EchoProtocol接收到一个行时,就会返回收到的行,除非遇到了'quit'将退出。
下一步,定义了EchoServerFactory类。EchoServerFactory继承自ServerFactory,作为ClientFactory的服务器端,并设置了EchoProtocol为protocol属性。EchoServerFactory的实例作为第二个参数传递给reactor.listenTCP,第一个参数是端口号。
 
Twisted网络编程必备(七)
 
 
3.0 WEB客户端
 
大部分上网活动都是通过WEB浏览器来访问WEB的。所以通过HTTP协议制作客户端来访问WEB是很有意义的。这一章讲解如何使用twisted.web.client模块来操作互联网资源,包括下载页面,使用HTTP认证,上传文件,使用HTTP字段等。
3.1 下载网页
 
最简单和常用的任务莫过于通过WEB客户端来下载网页了。客户端连接服务器,发送HTTP的GET请求,接收包含网页的HTTP响应。
 
3.1.1 下面如何做?
 
可以依靠Twisted内置的协议来快速进入工作。twisted.web包包含了完整的HTTP实现,免去自行开发Protocol和ClientFactory的工作。在未来,它还将包括建立HTTP请求的工具函数。获取一个网页,使用twisted.web.client.getPage。例子3-1是webcat.py脚本,用于通过URL获得网页。
from twisted.web import client
from twisted.internet import reactor
import sys
def printPage(data):
    print data
    reactor.stop()
def printError(failure):
    print >> sys.stderr, "Error: ", failure.getErrorMessage()
    reactor.stop()
if len(sys.argv)==2:
    url=sys.argv[1]
    client.getPage(url).addCallback(printPage).addErrback(printError)
    reactor.run()
else:
    print "Usage: webcat.py "
给webcat.py脚本一个URL,将会显示网页代码:
$ python webcat.py
 
3.1.2 它们如何工作?
 
函数printPage和printError是简单的事件处理器,用于打印下载的网页和错误信息。最重要的一行是client.getPage(url)。这个函数返回了Deferred对象,用于非同步状态下通知下载完成事件。
注意如何在一行中添加Deferred的回调函数。这是可能的,因为addCallback和addErrback都返回Deferred对象的引用。因此语句:
d=deferredFunction()
d.addCallback(resultHandler)
d.addCallback(errorHandler)
等同于:
deferredFunction().addCallback(resultHandler).addErrback(errorHandler)
这两种方式都可以用,下面那种方式在Twisted代码中更加常见。
 
3.1.3 关于
 
是否有需要将网页写入磁盘上呢?这个高级功能在使用3-1例子脚本下载巨大文件时可能会出问题。一个更好的解决方法是在下载时将数据写入临时文件,而在下载完成时在从临时文件中全部读出。
twisted.web.client包含了downloadPage函数,类似于getPage,但是将文件写入文件。调用downloadPage并传入URL做首参数,文件名或文件对象做第二个参数。如下例3-2:
from twisted.web import client
import tempfile
def downloadToTempFile(url):
    """
    传递一个URL,并返回一个Deferred对象用于下载完成时的回调
    """
    tmpfd,tempfilename=tempfile.mkstemp()
    os.close(tmpfd)
    return client.downloadPage(url,tempfilename).addCallback(returnFilename,tempfilename)
def returnFilename(result,filename):
    return filename
if __name__=='__main__':
    import sys,os
    from twisted.internet import reactor
    def printFile(filename):
        for line in file(filename,'r+b'):
            sys.stdout.write(line)
        os.unlink(filename) #删除文件
        reactor.stop()
    def printError(failure):
        print >> sys.stderr, "Error: ",failure.getErrorMessage()
        reactor.stop()
    if len(sys.argv)==2:
        url=sys.argv[1]
        downloadToTempFile(url).addCallback(printFile).addErrback(printError)
        reactor.run()
    else:
        print "Usage: %s "%sys.argv[0]
函数downloadToTempFile在调用twisted.web.client.downloadPage时返回Deferred对象。downloadToTempFile还添加了returnFilename作为Deferred的回调,将临时文件名作为附加参数。这意味着当downloadToTempFile返回时,reactor将会调用returnFileName作为downloadToTempFile的首个参数,而文件名作为第二个参数。
例子3-2注册了另外两个回调函数到downloadToTempFile。记住downloadToTempFile返回的Deferred已经包含了returnFilename作为回调处理器。因此,在结果返回时,returnFilename将会首先被调用。这个函数的结果被用于调用printFile。
3.2 存取受到密码保护的页面
 
有些网页需要认证。如果你正在开发HTTP客户端应用,那么最好准备好处理这些,并在需要时给出用户名和密码。
 
3.2.1 下面如何做?
 
如果一个HTTP请求以401的状态码执行失败,则是需要认证的。这时传递用户提供的登录用户名和密码到Authorization字段,有如例子3-3中那样:
from twisted.web import client,error as weberror
from twisted.internet import reactor
import sys,getpass,base64
def printPage(data):
    print data
    reactor.stop()
def checkHTTPError(failure,url):
    failure.trap(weberror.Error)
    if failure.value.status=='401':
        print >> sys.stderr,failure.getErrorMessage()
        #要求用户名和密码
        username=raw_input("User name: ")
        password=getpass.getpass("Password: ")
        basicAuth=base64.encodestring("%s:%s"%(username,password))
        authHeader="Basic "+basicAuth.strip()
        #尝试再次获取页面,已经加入验证信息
        return client.getPage(url,headers={"Authorization":authHeader})
    else:
        return failure
def printError(failure):
    print >> sys.stderr, 'Error: ',failure.getErrorMessage()
    reactor.stop()
if len(sys.argv)==2:
    url=sys.argv[1]
    client.getPage(url).addErrback(
        checkHTTPError,url).addCallback(
        printPage).addErrback(
        printError)
    reactor.run()
else:
    print "Usage: %s "%sys.argv[0]
运行webcat3.py并传递URL作为附加参数,将会尝试下载页面。如果收到了401错误,则会询问用户名和密码之后自动重试:
$ python webcat3.py
401 Authorization Required
User name: User
Password: <输入密码>
... ...
 
3.2.2 它们如何工作?
 
这个例子使用了扩展的错误处理器。它首先给client.getPage添加了Deferred,在添加printPage和printError之前。这样做给了checkHTTPError以机会在其他处理器之前来处理client.getPage的错误。
作为一个errback的错误处理器,checkHTTPError将被twisted.python.failure.Failure对象调用。Failure对象封装了跑出的异常,记录了异常时的traceback,并添加了几个有用的方法。checkHTTPError开始使用了Failure.trap方法来证实异常是twisted.web.error.Error类型的。如果不是,trap将会重新抛出异常,退出当前函数并允许错误传递到下一个errback处理器,printError。
然后,checkHTTPError检查HTTP响应状态码.failure.value是一个Exception对象,并且是twisted.web.error.Error的对象,已知的status属性包含了HTTP响应状态码。如果状态码不是401,则返回原始错误,通过把错误传递给printError。
如果状态码是401,checkHTTPError会执行。它会提示输入用户名和密码,并编码成HTTP的Authorization头字段。然后调用client.getPage,返回Deferred对象。这将导致一些很cool的事情发生,反应器等待第二次调用结果,然后调用printPage或者printError来处理结果。实际上,checkHTTPError将会提示"通过另外一个Deferred来处理错误,等待Deferred的结果,并且在另外一行中进行事件处理"。这个技术是很强大的,并且可以在Twisted程序中多次使用。
最终的结果有如前面的例子:使用printPage或者printError来输出结果。当然,如果初始化请求成功了,则不需要认证,checkHTTPError将不会被调用,返回的结果也将直接调用printPage。
 
Twisted网络编程必备(八)
 
 
3.3 上传文件
 
以用户的观点来说,没有什么简单的方法使得上传文件到网页。使用HTML表单选择文件并按下提交按钮才可以上传。正因为如此很多站点提供了难用的上传方式。有时当你需要上传文件而又不能使用浏览器时。可能需要自己开发程序来上传照片、基于WEB的文件管理系统等等。下面的例子展示了使用Twisted开发HTTP客户端来上传文件的方法。
 
3.3.1 下面如何做?
 
首先,对键值对编码,并将上传文件编入multipart/form-data的MIME文件。Python和Twisted都无法提供简便的方法来实现,但你可以也可以不需要太大努力的自己实现一个。然后传递已编码的表单数据到formdata键,并传递给client.getPage或client.downloadPage,并使用HTTP的POST方法。然后就可以执行getPage或downloadPage来获得HTTP响应了。例子3-4(validate.py)展示了如何按照W3C标准上传文件,并保存响应到本地文件,然后显示在用户浏览器中。
from twisted.web import client
import os,tempfile,webbrowser,random
def encodeForm(inputs):
    """
    传递一个字典参数inputs并返回multipart/form-data字符串,包含了utf-8编码的
    数据。键名必须为字符串,值可以是字符串或文件对象。
    """
    getRandomChar=lambda:chr(random.choice(range(97,123)))
    randomChars=[getRandomChar() for x in range(20)]
    boundary="---%s---"%''.join(randomChars)
    lines=[boundary]
    for key,val in inputs.items():
        header='Content-Disposition: form-data; name="%s"'%key
        if hasattr(val,'name'):
            header+=';filename="%s"'%os.path.split(val.name)[1]
        lines.append(header)
        if hasattr(val,'read'):
            lines.append(val.read())
        else:
            lines.append(val.encode('utf-8'))
        lines.append('')
        lines.append(boundary)
    return "\r\n".join(lines)
def showPage(pageData):
    #将数据写入临时文件并在浏览器中显示
    tmpfd,tmp=tempfile.mkstemp('.html')
    os.close(tmpfd)
    file(tmp,'w+b').write(pageData)
    webbrowser.open('file://'+tmp)
    reactor.stop()
def handleError(failure):
    print "Error: ",failure.getErrorMessage()
    reactor.stop()
if __name__=='__main__':
    import sys
    from twisted.internet import reactor
    filename=sys.argv[1]
    fileToCheck=file(filename)
    form=encodeForm({'uploaded_file':fileToCheck})
    postRequest=client.getPage(
        '',
        method="POST",
        headers={'Content-Type':'multipart/form-data; charset=utf-8',
                 'Content-Length':str(len(form))},
        postdata=form)
    postRequest.addCallback(showPage).addErrback(handleError)
    reactor.run()
运行validate.py脚本并在第一个参数给出HTML文件名:
$ python validate.py test.html
一旦发生了错误,必须获得validation的在浏览器中的错误报告。
 
3.3.2 它们是如何做的?
 
按照W3C标准页面,例如包含表单如下:
   
阅读(2830) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~