Twisted网络编程必备(2)


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

使用异步的方式工作的结果

 

除了反应器reactor之外,Deferred可能是最有用的Twisted对象。你可能在Twisted程序中多次用到Deferred,所有有必要了解它是如何工作的。Deferred可能在开始的时候引起困惑,但是它的目的是简单的:保持对非同步活动的跟踪,并且获得活动结束时的结果

Deferred可以按照这种方式说明:可能你在饭店中遇到过这个问题,如果你在等待自己喜欢的桌子时,在一旁哼哼小曲。带个寻呼机是个好主意,它可以让你在等待的时候不至于孤零零的站在那里而感到无聊。你可以在这段时间出去走走,到隔壁买点东西。当桌子可用时,寻呼机响了,这时你就可以回到饭店去你的位置了。

一个Deferred类似于这个寻呼机。它提供了让程序查找非同步任务完成的一种方式,而在这时还可以做其他事情。当函数返回一个Deferred对象时,说明获得结果之前还需要一定时间。为了在任务完成时获得结果,可以为Deferred指定一个事件处理器。

 

当编写一个启动非同步操作的函数时,返回一个Deferred对象。当操作完成时,调用Deferred的callback方法来返回值。如果操作失败,调用Deferred.errback函数来跑出异常。例子2-4展示了程序使用Deferred使用非同步操作的连接例子,连接一个服务器的端口。

当调用一个可以返回Deferred的函数时,使用Deferred.addCallback方法指定返回结果时调用的函数。使用Deferred.addErrback方法指定执行发生异常时调用的函数。

复制代码
代码
     
     
from twisted.internet.selectreactor import SelectReactor from twisted.internet.protocol import Protocol,ClientFactory,defer reactor = SelectReactor() class CallbackAndDisconnectProtocol(Protocol): def connectionMade(self): self.factory.deferred.callback( " Connected! " ) class ConnectionTestFactory(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.

 

它是如何工作的?

 

类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,打印西那个关的信息并停止反应器。

 

关于

 

是否需要保持一串的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

... ...

例子使用了Python的列表(list)的形式来创建一个列表存储由testConnect()返回的Deferred对象的列表。每一个testConnect()使用host参数,并测试1-200端口。

testers=[testConnect(host,port) for port in ports]

例子使用列表方式包装了Deferred对象成为DeferredList对象。DeferredList将会跟踪所有的Deferred对象的结果并传递作为首参数。当它们都完成了的时候,将会回调按照(success,result)的格式。在Deferred完成时,第一个回传值是TRue和第二个参数是Deferred传回结果。如果执行失败,则第一个参数是False且第二个参数是Failure对象包装的异常。consumeErrors键设置为True时告知DeferredList完成了吸收Deferred的所有错误。否则将会看到弹出的错误信息。

当DeferredList完成时,results将会被传给handleAllResults函数,列表中的每个元素都会被扫描。handleAllResults使用了zip函数来匹配每个结果的端口。对每一个端口如果有成功的连接则打印信息。最终反应器会结束并结束程序。

 

发送和接收数据

 

一旦TCP连接被建立,就可以用于通讯。程序可以发送数据到另一台计算机,或者接收数据。

 

使用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-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.

 

它们是如何工作的?

Twisted服务器使用相同的Protocol类作为客户端。为了复用,EchoProtocol继承了twisted.protocols.basic.LineReceiver,作为Protocol.LineReceiver的轻量级实现,可以自动按照获得的行来产生处理。当EchoProtocol接收到一个行时,就会返回收到的行,除非遇到了'quit'将退出。

下一步,定义了EchoServerFactory类。EchoServerFactory继承自ServerFactory,作为ClientFactory的服务器端,并设置了EchoProtocol为protocol属性。EchoServerFactory的实例作为第二个参数传递给reactor.listenTCP,第一个参数是端口号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值