学习了socket后决定尝试使用框架,目标锁定了Twisted。
什么是Twisted?
twisted是一个用python语言写的事件驱动的网络框架,他支持很多种协议,包括UDP,TCP,TLS和其他应用层协议,比如HTTP,SMTP,NNTM,IRC,XMPP/Jabber。 非常好的一点是twisted实现和很多应用层的协议,开发人员可以直接只用这些协议的实现。其实要修改Twisted的SSH服务器端实现非常简单。很多时候,开发人员需要实现protocol类。
一个Twisted程序由reactor发起的主循环和一些回调函数组成。当事件发生了,比如一个client连接到了server,这时候服务器端的事件会被触发执行。
基于事件:
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。
来比较和对比一下单线程、多线程以及事件驱动编程模型。图21.1展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。
在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。
在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。
在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题
体现在代码中,在定义协议时,事件方法会在相应事件发生时被调用,比如在接收新的连接或丢失连接时:
def connectionMade(self): #在得到连接时触发 self.transport.write('ftp-server: welcome to ftp!') #输出信息到客户端 print 'Got connection from ',self.transport.client def connectionLost(self, reason): #在失去连接时触发 print self.transport.client,'disconnect'
当有数据需要被接收时,dataReceived方法被调用:
def dataReceived(self,data) print data
如果想实现只接收了一整行就调用事件处理方法可以使用lineReceived方法。注意,之后每接收一行都会调用一次事件方法...
def lineReceived(self, line): print line
异步:
Twisted 官方称,“Twisted is event-based, asynchronous framework ”。这个“异步”功能的代表就是 deferred
deferred 的作用类似于“多线程”,负责保障多头连接、多项任务的异步执行。
但deferred “异步”功能的实现,与多线程完全不同,具有以下特点:
1. deferred 产生的 event,是函数调用返回的对象;
2. deferred 代表一个连接任务,负责报告任务执行的延迟情况和最终结果;
3. 对deferred 的操作,通过预定的“事件响应器”(event handler)进行。
有了deferred,即可对任务的执行进行管理控制。防止程序的运行,由于等待某项任务的完成而陷入阻塞停滞,提高整体运行的效率。
defered貌似有很多实现方式,我只学会了下面这种
def upload(self,path,content): #定义一个根据客户端请求写文件的方法,写完后生成一个hash码用于验证 with open(path,'wb') as f: f.write(content) with open(path,'rb') as f: #hmac验证 h = hmac.new('liqixuan') h.update(f.read()) file_hash = h.hexdigest() print(file_hash) self.sendLine(file_hash) d = threads.deferToThread(self.upload,'D:/client_new.py',s) #生成defered对象,异步调用写文件方法 d.addCallbacks(self.finash,self.failed)
刚接触框架,还一头雾水,继续学习中,贴上一个练手代码:
功能:异地上传文件,文件异步传输,通过hash码验证文件的完整性
server:
#!/usr/bin/env python #-*- coding:utf-8 - from twisted.internet import reactor from twisted.internet.protocol import Protocol,Factory from twisted.protocols.basic import LineOnlyReceiver import os,hmac from twisted.internet import defer,threads ''' 定义一个协议类,在一个新的连接到达时,创建这个协议对象的factory。 此协议类继承LineOnlyReceiver,在得到连接时触发connectionMade方法, 失去连接时触发connectionLost方法,接收数据时会每接收一行就触发一次lineReceived方法。 使用defered异步处理文件I/O ''' class MyTwisted(Protocol): d def connectionMade(self): #在得到连接时触发 self.transport.write('ftp-server: welcome to ftp!') #输出信息到客户端 self.path = '' #初始化一些字段 self.li = [] self.begin = False self.end = False print 'Got connection from ',self.transport.client def connectionLost(self, reason): #在失去连接时触发 print self.transport.client,'disconnect' def finash(self,arg): #定义两个回调方法,对应执行成功和失败 print 'Finashed!' def failed(self,arg): print 'Failed' def upload(self,path,content): #定义一个根据客户端请求写文件的方法,写完后生成一个hash码用于验证 with open(path,'wb') as f: f.write(content) with open(path,'rb') as f: #hmac验证 h = hmac.new('liqixuan') h.update(f.read()) file_hash = h.hexdigest() print(file_hash) self.sendLine(file_hash) def lineReceived(self, line): ''' 由于每接收一行就会调用该方法,故每次需要对传输内容进行判断 ''' #接收客户端发送的上传文件路径 if line.startswith('path_filename'): self.path = line.replace('path_filename','') if os.path.isdir(line): self.sendLine('True') #反馈客户端 else: self.sendLine('False') #反馈客户端 ''' 开始接收文件内容时触发的事件 ''' if line.startswith('*begin*'): #设置一个文件开始位,标志开始传输文件内容 self.begin = True line = line.replace('*begin*','') if '*end*' in line and '*begin*' not in line: #结束位 self.end = True line = line.replace('*end*','') if self.begin: #为结束前将文件内容写进某个字段 self.li.append('%s\r\n' % line) if self.end: #收到结束标志后开始准备传输文件 self.begin = False #重置标志位 self.end = False s = ''.join(self.li[:-1]) #去掉最后换行符 d = threads.deferToThread(self.upload,'D:/client_new.py',s) #生成defered对象,异步调用写文件方法 d.addCallbacks(self.finash,self.failed) #执行完毕后回调相应方法 factory = Factory() factory.protocol = MyTwisted reactor.listenTCP(8000,factory) reactor.run()
client:
#-*- coding:utf-8 -*- import socket,os,hmac def upload(): local_path = raw_input('本地文件: ').strip() if os.path.isfile(local_path): upload_path = raw_input('文件上传路径: ').strip() file_name = os.path.basename(local_path) sk.send('path_filename%s%s\r\n' % (upload_path,file_name)) #发送目标路径及文件名 answer = sk.recv(1024) #接收反馈,若路径不存在退出 if answer == 'False': print('No such file or directory') return else: print('ready to upload...') with open(local_path,'rb')as f: #若存在读文件 s = f.read() s = '*begin*%s*end*\r\n' % s #添加首尾标志位并发送服务器 sk.send(s) file_hash = sk.recv(1024) #接收反馈的hash码 file_hash = file_hash.replace('\r\n','') print("target file's HASH is ",file_hash) with open(local_path,'rb') as f: #得到原文件hash码 h = hmac.new('liqixuan') h.update(f.read()) hash1 = h.hexdigest() print("local file's HASH is",hash1) if hash1 == file_hash: #若一致则上传成功 print 'file upload successful!' else: print 'file upload failed!' ip_port = ('127.0.0.1',8000) sk = socket.socket() sk.connect(ip_port) server_data = sk.recv(1024) print(server_data) upload()