Twisted随笔

学习了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()

 

 

 

  

 

posted @ 2015-12-19 11:07  瓶子里的人  阅读(321)  评论(0编辑  收藏  举报