本文章通过实际例子和用Twisted实现一个类似QQ
那样的聊天室来逐步学习Twisted
一、稍微来点基础
首先,我们要明确一个概念,Twisted
是一个完全事件驱动的网络框架.它允许你使用和开发完全异步的网络应用程序和协议.
Twisted
常用函数:
connectionMade()
:函数:这个函数在客户端成功连接的时候被调用,即仅仅在连接成功的时候会调用一次
dataReceived()
:函数:这个函数在客户端通过网络发送数据过来的时候被调用.reactor
把数据当成参数传到这个函数中,这样我们就不用自己去解析数据了
self.transport
:一个Twisted的transport
代表一个可以收发字节的单条连接.例如ChatProtocol
的服务器它的此属性为<ChatProtocol #0 on 8123>
self.transport.loseConnection():
断掉与服务器之间的连接
self.transport.getPeer
:获得主机(服务器)信息例如:IPv4Address(TCP, '127.0.0.1', 35836)
依次是 协议,IP地址,服务器端口号
self.transport.getPeer().host
:获得主机信息的IP地址
self.transport.client
属性:是一个元组,其中包含了客户端的地址(IP,端口)例如:('127.0.0.1',35753)
protocol.Factory()
:它被称为工厂,每次连接进来的时候,它都会"生产"一个我们的protocol
对象.然后在reactor
中安装一个TCP监听器以等待服务请求.当有请求进来时,创建一个DemoProtocol实例来服务那个客户端.
具体的一个小例子如下:
# coding=utf-8
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor
PORT = 21567
class EchoProtocol(Protocol):
def connectionMade(self):
print 'Got connection from', self.transport.client
def connectionLost(self, reason):
print self.transport.client, 'disconnected'
def dataReceived(self, data):
print self.transport.write(data)
factory = Factory()# 实例化Factory
factory.protocol = EchoProtocol # 设置factory的protocol属性以便它知道使用哪个protocol与客户端通信(这就是所谓的你的自定义protocol)
reactor.listenTCP(PORT, factory)
reactor.run()
对于客户端部分,需要注意的是:
class EchoClientFactory(protocol.clientFactory):
protocol = EchoClientProtocol
clientConnectionLost = clientConnectionFaild= \
lambda self,connector,reason:reactor.stop()
reactor.connectTCP(HOST,PORT,EchoClientFactory())
reactor.run()
脚本的最后一部分是创建一个客户端工厂,连接到服务器,然后运行reactor
.注意,我们在这里实例化了客户端工厂,而不是像在服务器里那样把它传到reactor
中.这是因为,我们不是等待客户端连接的服务器,服务器在有连接时要为每个连接创建一个新的protocol
对象.我们只是一个客户端,所以我们只要创建一个protocol
对象,连接到服务器,服务器的工厂会创建一个protocol
对象来与我们对话
二、提升效率的 defferred
Twisted 官方称,“Twisted is event-based, asynchronous framework ”。
这个“异步”功能的代表就是 defferred。 defferred 的作用类似于“多线程”,负责保障多头连接、多项任务的异步执行。
当然,defferred “异步”功能的实现,与多线程完全不同,具有以下特点:
- defferred 产生的 event,是函数调用返回的对象;
- defferred 代表一个连接任务,负责报告任务执行的延迟情况和最终结果;
- 对defferred 的操作,通过预定的“事件响应器”(event handler)进行。 有了defferred,即可对任务的执行进行管理控制。防止程序的运行,由于等待某项任务的完成而陷入阻塞停滞,提高整体运行的效率。
请看下面的例子: 建议只关注黑体字的语句,它们反映了defferred的用法。
涉及的两个class,是Twisted建立网络连接的固定套路,后面会专门说它。
# connectiontest.py
from twisted.internet import reactor, defer,protocol
class CallbackAndDisconnectProtocol(protocol.Protocol):
# Twisted建立网络连接的固定套路
def connectionMade(self):
self.factory.deferred.callback("Connected!")# “事件响应器”handleSuccess对此事件作出处理
self.transport.loseConnection( )
class ConnectionTestFactory(protocol.ClientFactory):
# Twisted建立网络连接的固定套路
protocol = CallbackAndDisconnectProtocol
def __init__(self):
self.deferred = defer.Deferred( )# 报告发生了延迟事件,防止程序阻塞在这个任务上
def clientConnectionFailed(self, connector, reason):
self.deferred.errback(reason) # “事件响应器”handleFailure对此事件作出处理
def testConnect(host, port):
testFactory = ConnectionTestFactory( )
reactor.connectTCP(host, port, testFactory)
return testFactory.deferred # 返回连接任务的deferred
def handleSuccess(result, port):# deferred“事件响应器”:连接任务完成的处理
print "Connected to port %i" % port reactor.stop( )
def handleFailure(failure, port): # deferred“事件响应器”:连接任务失败的处理 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)** # 调用函数,返回deferred **connecting.addCallback(handleSuccess, port)** # 建立deferred“事件响应器” **connecting.addErrback(handleFailure, port)** # 建立deferred“事件响应器” reactor.run( )
三、创建 client 的套路
第二节说到的两个类,是TCP协议客户端的创建套路(方式)。这个套路拆解如下:
- 定义“工厂”和“协议”两个类:
(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)在“协议”类中,重设父类函数connectionMade
,由对象factory
引用“工厂”类中的deferred,经其引发事件,报告连接正常:
self.factory.deferred.callback("Connected!")
并由对象transport引发事件,报告连接断开:
self.transport.loseConnection( );
上述“对象”,都是从各自父类继承来的。 - 在函数
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.)。
我的感受,造成“最陡”的原因,是由于套路新颖独特,初学乍练不易适应。
- 框架对象众多,一时记不牢;
- 对象之间的关系比较复杂,一时理不清;
- “事件驱动”这种模式,反映在程序文本中,有时见不到明显的函数调用,让人觉得程序的去向不明;
另外,学习方法很重要。如果以学“语言”的习惯来学框架,遇上问题寻根究底,过分追求“水落石出”;或者,依赖教科书、畸重“理论”,忽视 examples 语句、结构和API文档的分析研究,都不利于翻越这段陡坡.我的体验,集中精力地啃嚼主干骨架,不纠緾于细枝末节,这段最陡的上坡路,顶多爬个十天八天的,就能越过去。