Python自动化开发从浅入深-进阶(Twisted、Reactor)
Twisted 是用Python实现的基于事件驱动的网络引擎框架。
Twisted 诞生于2000年初,在当时的网络游戏开发者看来,无论他们使用哪种语言,手中都鲜有可兼顾扩展性及跨平台的网络库。Twisted的作者试图在当时现有的环境下开发游戏,这一步走的非常艰难,他们迫切地需要一个可扩展性高、基于事件驱动、跨平台的网络开发框架,为此他们决定自己实现一个,并从那些之前的游戏和网络应用程序的开发者中学习,汲取他们的经验教训。
Twisted 支持许多常见的传输及应用层协议,包括TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。就像Python一样,Twisted也具有“内置电池”(batteries-included)的特点。Twisted对于其支持的所有协议都带有客户端和服务器实现,同时附带有基于命令行的工具,使得配置和部署产品级的Twisted应用变得非常方便。
一. Twisted基础
--异步编程模型
事件驱动编程是一种编程范式,程序的执行流由外部事件来决定。
特点:包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。(另外两种常见的编程范式是(单线程)同步以及多线程编程)
在单线程下,异步模型任务是交错完成的。
异步编程模型与多线程模型之间的区别:
1. 多线程:对线程的操纵权不在编程者手中,而在操作系统那里,因此,程序员在编写程序过程中必须要假设在任何时候一个线程都有可能被停止而启动另外一个线程。
2. 异步模型:所有事件是以异步的方式到达,然后CPU同样以异步的方式从Cache队列中取出事件进行处理,一个任务要想运行必须显式放弃当前运行的任务的控制权。这也是相比多线程模型来说,最简洁的地方
--异步编程优点
1. 在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。
2. 在多线程版本中,任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙的BUG。
1. 有大量的任务,以至于可以认为在一个时刻至少有一个任务要运行。 2. 任务执行大量的I/O操作,这样同步模型就会在因为任务阻塞而浪费大量的时间。 3. 任务之间相互独立,以至于任务内部的交互很少。
二. 异步编程模式与Reactor
1. 异步模式客户端一次性与全部服务器完成连接,而不像同步模式那样一次只连接一个,连接完成后等待新事件的到来。
2. 用来进行通信的Socket方法是非阻塞模式的,这是通过调用setblocking(0)来实现的。
3. select模块中的select方法是用来识别其监视的socket是否有完成数据接收的,如果没有它就处于阻塞状态。
4. 当从服务器中读取数据时,会尽量多地从Socket读取数据直到它阻塞为止,然后读下一个Socket接收的数据(如果有数据接收的话)。这意味着我们需要跟踪记录从不同服务器传送过来数据的接收情况。
以上过程可以被设计成为一个模式: reactor模式
这个循环就是个"reactor"(反应堆),因为它等待事件的发生然后对其作出相应的反应。正因为如此,它也被称作事件循环。由于交互式系统都要进行I/O操作,因此这种循环也有时被称作select loop,这是由于select调用被用来等待I/O操作。因此,在程序的select循环中,一个事件的发生意味着一个socket端处有数据来到,如下:
1. 监视一系列sockets(文件描述符)
2. 并阻塞程序
3. 直到至少有一个准备好的I/O操作
一个真正reactor模式的实现是需要实现循环独立抽象出来并具有如下的功能
1. 监视一系列与I/O操作相关的文件描述符(description)
2. 不停地汇报那些准备好的I/O操作的文件描述符
3. 处理所有不同系统会出现的I/O事件
4. 提供优雅的抽象来帮助在使用reactor时少花些心思去考虑它的存在
5. 提供可以在抽象层外使用的公共协议实现
Twisted实现了设计模式中的反应堆(reactor)模式,这种模式在单线程环境中调度多个事件源产生的事件到它们各自的事件处理例程中去。
Twisted的核心就是reactor事件循环。Reactor可以感知网络、文件系统以及定时器事件。它等待然后处理这些事件,从特定于平台的行为中抽象出来,并提供统一的接口,使得在网络协议栈的任何位置对事件做出响应都变得简单。
基本上reactor完成的任务就是
while True: timeout = time_until_next_timed_event() events = wait_for_events(timeout) events += timed_events_until(now()) foreventin events: event.process()
twisted_EchoServer.py
#!/usr/bin/env python #__*__coding:utf-8__*__ from twisted.internet import protocol from twisted.internet import reactor # Transports # # Transports : 代表网络中两个通信结点之间的连接。 # Transports 负责描述连接的细节,比如连接是面向流式的还是面向数据报的,流控以及可靠性。TCP、UDP和Unix套接字可作为transports的例子。 # 它们被设计为“满足最小功能单元,同时具有最大程度的可复用性”,而且从协议实现中分离出来,这让许多协议可以采用相同类型的传输。 # Transports实现了ITransports接口,它包含如下的方法: # # write 以非阻塞的方式按顺序依次将数据写到物理连接上 # writeSequence 将一个字符串列表写到物理连接上 # loseConnection 将所有挂起的数据写入,然后关闭连接 # getPeer 取得连接中对端的地址信息 # getHost 取得连接中本端的地址信息 # 将transports从协议中分离出来也使得对这两个层次的测试变得更加简单。可以通过简单地写入一个字符串来模拟传输,用这种方式来检查。 # Protocols # # Protocols描述了如何以异步的方式处理网络中的事件。HTTP、DNS以及IMAP是应用层协议中的例子。Protocols实现了IProtocol接口,它包含如下的方法: # makeConnection 在transport对象和服务器之间建立一条连接 # connectionMade 连接建立起来后调用 # dataReceived 接收数据时调用 # connectionLost 关闭连接时调用 #定义类(继承Protocol) class Echo(protocol.Protocol): def dataReceived(self, data):#接收数据时调用 self.transport.write(data)#以非阻塞的方式按顺序依次将数据写到物理连接上 # reactor是twisted事件循环的核心,它提供了一些服务的基本接口,像网络通信、线程和事件的分发。 # # 一个真正reactor模式的实现是需要实现循环独立抽象出来并具有如下的功能 # 1.监视一系列与I / O操作相关的文件描述符(description) # 2.不停地汇报那些准备好的I / O操作的文件描述符 # 3.处理所有不同系统会出现的I / O事件 # 4.提供优雅的抽象来帮助在使用reactor时少花些心思去考虑它的存在 # 5.提供可以在抽象层外使用的公共协议实现 #Twisted的核心就是reactor事件循环。Reactor可以感知网络、文件系统以及定时器事件。它等待然后处理这些事件, # 从特定于平台的行为中抽象出来,并提供统一的接口,使得在网络协议栈的任何位置对事件做出响应都变得简单 #dir(reactor) #============ #['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__implemented__', '__init__', '__module__', '__name__', '__new__', # '__providedBy__', '__provides__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cancelCallLater', # '_cancellations', '_checkProcessArgs', '_childWaker', '_disconnectSelectable', '_doReadOrWrite', '_eventTriggers', '_handleSignals', '_initThreadPool', '_initThreads', # '_insertNewDelayedCalls', '_installSignalHandlers', '_internalReaders', '_justStopped', '_lock', '_makeHelperReactor', '_moveCallLaterSooner', '_newTimedCalls', # '_pendingTimedCalls', '_preenDescriptors', '_reactor', '_reactorThread', '_reads', '_reallyStartRunning', '_registerAsIOThread', '_removeAll', '_started', '_startedBefore', # '_stopThreadPool', '_stopped', '_threadpoolStartupID', '_uninstallHandler', '_unmakeHelperReactor', '_wakerFactory', '_writes', 'addEvent', 'addReader', 'addSystemEventTrigger', # 'addWriter', 'adoptDatagramPort', 'adoptStreamConnection', 'adoptStreamPort', 'callFromThread', 'callInThread', 'callLater', 'callWhenRunning', 'connectSSL', 'connectTCP', # 'connectUNIX', 'connectUNIXDatagram', 'crash', 'disconnectAll', 'doIteration', 'doSelect', 'fireSystemEvent', 'getDelayedCalls', 'getReaders', 'getThreadPool', 'getWriters', # 'installResolver', 'installWaker', 'installed', 'iterate', 'listenMulticast', 'listenSSL', 'listenTCP', 'listenUDP', 'listenUNIX', 'listenUNIXDatagram', 'mainLoop', 'removeAll', # 'removeEvent', 'removeReader', 'removeSystemEventTrigger', 'removeWriter', 'resolve', 'resolver', 'run', 'runUntilCurrent', 'running', 'seconds', 'sigBreak', 'sigInt', 'sigTerm', # 'spawnProcess', 'startRunning', 'stop', 'suggestThreadPoolSize', 'threadCallQueue', 'threadpool', 'threadpoolShutdownID', 'timeout', 'usingThreads', 'wakeUp', 'waker'] def main(): # 子类this指明你的协议,Factory仅仅在server上可用。Subclass this to indicate that your protocol.Factory is only usable for server factory = protocol.ServerFactory() # 将类Echo赋给protocol factory.protocol = Echo reactor.listenTCP(1234,factory)#开始监听端口,将factory作为参数传进去 reactor.run() #开始运行 if __name__ == '__main__': main()
twisted_EchoClient.py
#!/usr/bin/env python #__*__coding:utf-8__*__ from twisted.internet import reactor, protocol # Protocols # # Protocols描述了如何以异步的方式处理网络中的事件。HTTP、DNS以及IMAP是应用层协议中的例子。Protocols实现了IProtocol接口,它包含如下的方法: # makeConnection 在transport对象和服务器之间建立一条连接 # connectionMade 连接建立起来后调用 # dataReceived 接收数据时调用 # connectionLost 关闭连接时调用 # a client protocol #继承protocol,处理reactor事件循环中事件到来的处理 class EchoClient(protocol.Protocol): """Once connected, send a message, then print the result. 客户端对事件的处理:包括: 1、连接建立起来的事件处理 2、数据到来时的事件处理 3、没有连接时的事件处理 """ def connectionMade(self):#连接建立起来后调用 self.transport.write("hello alex!") def dataReceived(self, data):#数据到来,接收数据时调用 "As soon as any data is received, write it back." print "Server said:", data self.transport.loseConnection() def connectionLost(self, reason):#没有连接时的调用 print "connection lost" #客户端工厂,处理连接 class EchoFactory(protocol.ClientFactory): protocol = EchoClient # 将EchoClient协议实例化为protocol #客户端连接失败时的处理 def clientConnectionFailed(self, connector, reason): print "Connection failed - goodbye!" reactor.stop() #丢失客户端连接时的处理 def clientConnectionLost(self, connector, reason): print "Connection lost - goodbye!" reactor.stop() # this connects the protocol to a server running on port 8000 def main(): f = EchoFactory() #对客户端工厂实例化 reactor.connectTCP("localhost", 1234, f)#为reactor事件循环指定通讯参数 reactor.run()#reactor事件循环开始运行 # this only runs if the module was *not* imported if __name__ == '__main__': main()