Python案例-网络编程-FTP&断点续传&进度条&上传下载

本次ftpV2,完善了上次未完成的代码,实现上传下载,断点续传,客户端任务进度条等功能
废话不多说,上代码,详细解释,请查看代码注释

Server端

#!/usr/bin/env python
#   --coding = utf-8
#   Author Allen Lee
import socketserver,json,os
import subprocess

#创建ftpserver类,并继承socktserver,借用其io多路复用的select方法,以实现多响应用户的并发
class Myftp(socketserver.BaseRequestHandler):
    #再次重写handle方法,由于类的继承中,self遵循自下而上,从左到右的寻址规则,因此被重写的方法将优先被调用
    def handle(self):
        #权限控制
        while True:
            if not self.checkuser():
                break
            else:
                self.request.sendall(bytes('welcome to myftp',encoding='utf-8'))
            while True:
                res_data = self.request.recv(1024).strip()
                if not res_data:
                    break
                #再次统一处理client端发出的json包,
                res_msg = json.loads(str(res_data,encoding='utf-8'))
                #通过反射来引导用户到对应的方法中
                res_tag = res_msg.get('action')
                if hasattr(self,res_tag):
                    getattr(self,res_tag)(res_msg)
                else:break
            self.request.close()
    #用户验证
    def checkuser(self):
        server_tag = False
        res = self.request.recv(1024)
        if len(res) == 0:
            return error
        userinfo = json.loads(str(res,encoding='utf-8'))
        if userinfo[0].get('username') == 'username' and userinfo[0].get('password') == 'password':
            server_tag = True
            return True
        else:
            return False
    def put(self,res_msg):
        file_name = res_msg.get('file_name')
        file_size = res_msg.get('file_size')
        ret = os.path.isfile(file_name)
        if not ret:
            #此为正常传输
            with open(file_name,'wb+')as f:
                self.request.sendall(bytes('ok',encoding='utf-8'))
                recv_data = self.request.recv(1024)
                f.write(recv_data)
        else:
            #此为断点续传处理
            with open(file_name,'ab+')as f:
                file_newsize = len(file_name)
                self.request.sendall(bytes('continue|%s'%(file_newsize), encoding='utf-8'))
                recv_data = self.request.recv(1024)
                f.write(recv_data)

    def get(self,res_msg):
        file_name = res_mas.get('file_name')
        ret = os.path.isfile(file_name)
        if not ret:
            print('this file is not exists')
            return False
        else:
            total_size =os.stat(file_name).st_size
            with open(file_name,'rb')as f:
                self.request.sendall(bytes('ready|%d'%(total_size),encoding='utf-8'))
                reg = str(self.request.recv(1024),encoding='utf-8')
                if reg.startswith('ok'):
                    new_size = 0
                    for line in f:
                            client.sendall(bytes(line,encoding='utf-8'))
                            new_size += len(line)
                            if new_size >= total_size:break
                if reg.startswith('continue'):
                    new_size = reg.split('|')[1]
                    #使用seek方法来实现将文件指针放在上次断点处,并继续传输,进而完成断点续传的功能,第一个参数表示指针偏移位数,第二个参数表示,从文件开头开偏移
                    f.seek(new_size,0)
                    for line in f:
                        client.sendall(bytes(line,encoding='utf-8'))
                        new_size +=len(line)
                        if new_size >= total_size:break
    #次方法为处理寻常请求,用户可以在这里实现切换目录、删除文件、创建目录、等等
    def cmd(self,res_msg):
        cmd = res_msg.get("cmd")
        result = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
        ret = result.stdout.read()
        if not ret:
            send_data = 'there is no such commons'
            print(send_data)
        else:
            send_data = bytes(ret,encoding='utf-8')
        self.request.sendall('ready|%s'%(len(send_data)))
        ret = str(self.request.recv(1024),encoding=utf-8)
        if ret == 'start':
            self.request.sendall(send_data)

if __name__ == '__main__':
    #创建对象时使用ThreadindTCP,来实现多线程的处理
    ftpserver = socketserver.ThreadingTCPServer(('127.0.0.1', 9909), Myftp)
    ftpserver.serve_forever()

Client端

#!/usr/bin/env python
#   --coding = utf-8
#   Author Allen Lee
import socket, os, json,sys

class Myclient():
    #在构造方法中启动client的socket对象以及连接server端
    def __init__(self):
        self.client = socket.socket()
        self.client.connect(('127.0.0.1', 9909))
    #run为client的主方法,其中通过对用户输入的命令来判断具体操作,并通过反射将其引导到对应的方法
    def run(self):
        while True:
            #权限控制
            check = self.login()
            #通过while循环实现用户输入代码的复用
            while True:
                res_data = self.client.recv(1024).strip()
                if not res_data:break
                print(str(res_data, encoding='utf-8'))
                send_data = input('>> : ').strip()
                if not send_data:
                    continue
                if hasattr(self,send_data):
                    getattr(self,send_data)(send_data)
                #当用户输入的不是指定的put  get方法时,将被引致平常的cmd命令行,在此可以完成切换目录、创建文件、删除文件
                else:
                    getattr(self,'cmd')(send_data)
            self.client.close()
    #用户登录验证
    def login(self):
        self.tag = False
        for i in range(3):
            username = input('please input your username:')
            passwd = input('please input your passwd:')
            password = commons.md5(passwd)
            userinfo = {"username":username,"password":password }
            self.client.send(bytes(json.dumps(userinfo),encoding='utf-8'))
            ret = self.client.recv(1024)
            res_check = str(ret,encoding='utf-8')
            if res_check == 'ok':
                self.tag = True
                return True
            else:
                return False
            print("your username or passwd is wrong")
            self.client.close()
    def put(self,send_data):
            pathh = input('input the file `s path ;')
            #首先判断文件是否存在,通过os.path.isfile来实现
            ret = os.path.isfile(pathh)
            if not ret:
                print('the file is not exists')
                return False
            #如果存在,则将操作类型、文件名、文件大小以字典形势格式化,再以json的方法进行格式化
            else:
                #通过os.stat方法来取文件的大小
                file_size = os.stat(pathh).st_size
                file_name = pathh.split('\\')[-1]
                send_msg = {"action":send_data,
                            "filename":file_name,
                            "filesize":file_size,
                }
                self.client.sendall(bytes(json.dumps(send_msg),encoding='utf-8'))
                res_tag = self.client.recv(1024)
                if not res_tag:
                    return False
                if str(res_tag,encoding='utf-8').startswith('ok'):
                    #上传文件只用r就够了
                    with open(pathh,'rb') as f:
                        new_size = 0
                        for line in f:
                            self.client.sendall(bytes(line,encoding='utf-8'))
                            new_size += len(line)
                            #以下为实现传输文件时,显示当前进度条
                            ret = new_size/file_size
                            num = int(ret*100)
                            view = '\r [%-100s]%d%%'%("="*num,100,)
                            sys.stdout.write(view)
                            sys.stdout.flush()
                            if new_size >= file_size:
                                break
                if str(res_tag,encoding='utf-8').startswith('continue'):
                    with open(pathh, 'rb') as f:
                        #此处使用seek来进行文件指针的偏移,进而完成断点续传的功能
                        file_newsize=str(res_tag,encoding='utf-8').split('|')[1]
                        f.seek(file_name,0)
                        for line in f:
                            self.client.sendall(bytes(line,encoding='utf-8'))
                            new_size += len(line)
                            #以下为实现传输文件时,显示当前进度条
                            ret = new_size/file_size
                            num = int(ret*100)
                            #以下使用了sys模块的标准输出
                            view = '\r [%-100s]%d%%'%("="*num,100,)
                            sys.stdout.write(view)
                            sys.stdout.flush()
                            if new_size >= file_size:
                                break
    def get(self,send_data):
            get_name = input('input what your want:').strip()
            if not get_name:
                return False
            send_msg = {"action":send_data,
                        "filename":get_name
                        }
            self.client.sendall(bytes(json.dumps(send_msg),encoding='utf-8'))
            res_data = self.client.recv(1024)
            res_tag = str(res_data,encoding='utf-8')
            #客户端接收到服务端的确认信息ready之后开始传输,相当于tcp的三次握手中的ack包
            if res_tag.startswith('ready'):
                #通过判断本地是否有此文件,来确认是否启用断点续传
                if not os.path.isfile(get_name):
                    with open(file_name,'wb')as f:
                        self.client.sendall(bytes('start', encoding='utf-8'))
                        total_size = res_data.split('|')[1]
                        new_size = 0
                        while new_size 1024)
                            new_size += len(res_data)
                            f.write(res_data)
                            #以下为下载文件的进度条实现
                            ret = new_size/total_size
                            num = int(ret*100)
                            view = '\r [%-100s]%d%%'%('='*num,100)
                            sys.stdout.write(view)
                            sys.stdout.flush()
                else:
                    #如果本地不存在此文件将给服务端发送,断点续传的请求标签
                    with open(file_name,'ab')as f:
                        new_size = os.stat(file_name).st_size
                        self.client.sendall(bytes('continue|%d'%(new_size), encoding='utf-8'))
                        total_size = res_data.split('|')[1]
                        while new_size 1024)
                            new_size += len(res_data)
                            f.write(res_data)
                            #以下为下载文件的进度条实现
                            ret = new_size/total_size
                            num = int(ret*100)
                            view = '\r [%-100s]%d%%'%('='*num,100)
                            sys.stdout.write(view)
                            sys.stdout.flush()
    def cmd(self,send_data):
            send_msg = {"action":'cmd',
                        "cmd":send_data,
                        }
            self.client.sendall(bytes(json.dumps(send_msg), encoding='utf-8'))
            res_data = self.client.recv(1024)
            res_tag = str(res_data, encoding='utf-8')
            if res_tag.startswith('ready'):
                self.client.sendall(bytes('start', encoding='utf-8'))
                #以下为解决发送命令过程中出现的粘报现象,思路为提前告诉对方总大小
                total_size = res_data.split('|')[1]
                new_size = 0
                while new_size < total_size:
                    res_data = self.client.recv(1024)
                    new_size += len(res_data)
                    print(str(res_data, encoding='utf-8'))
    def client_exit(self):
        self.client.close()

if __name__ == '__main__':
    Myclient.run()

具体一些bug还未调试,主题代码思路已经实现

你可能感兴趣的:(Python案例,python开发之路)