python 多用户在线的FTP程序

要求:

1、用户加密认证

2、允许同时多用户登录

3、每个用户有自己的家目录 ,且只能访问自己的家目录

4、对用户进行磁盘配额,每个用户的可用空间不同

5、允许用户在ftp server上随意切换目录

6、允许用户查看当前目录下文件

7、允许上传和下载文件,保证文件一致性

8、文件传输过程中显示进度条

9、附加功能:支持文件的断点续传

README:

设计说明

1、client连接server端需要验证账号密码,密码使用MD5加密传输。

2、用户信息保存在本地文件中,密码MD5加密存储。磁盘配额大小也保存在其中。

3、用户连接上来后,可以执行命令如下

目录变更:cd /cd dirname / cd . /cd ..

文件浏览:ls

文件删除:rm filename

目录增删:mkdir dirname /rmdir dirname

查看当前目录:pwd

查看当前目录大小: du

上传文件:put filename

下载文件:get filename

4、涉及到目录的操作,用户登录后,程序会给用户一个“锚位”----以用户名字命名的家目录,使用户无论怎么操作,都只能在这个目录底下。而在发给用户的目录信息时,隐去上层目录信息。

5、用户在创建时,磁盘配额大小默认是100M,在上传文件时,程序会计算当前目录大小加文件大小是否会超过配额上限。未超过,上传;超过,返回磁盘大小不够的信息。磁盘配额可通过用户管理程序修改。

6、文件上传和下载后都会进行MD5值比对,验证文件是否一致。

7、服务端和客户端都有显示进度条功能,启用该功能会降低文件传输速度,这是好看的代价。

8、文件断点续传,暂未实现该功能。我想参照下载软件的做法,将文件下载到一半后断开的信息保存在日志中,然后在下次传输时,检测日志是否有该文件未传完的信息,然后在接着从断点开始传输。等我日后空闲了再补上。

暂且说到这,接下来是正式程序

试运行截图

代码如下:

1、服务端

server.conf

####用户端配置文件####
[DEFAULT]
logfile = ../log/server.log
usermgr_log = ../log/usermgr.log
upload_dir= ../user_files
db_dir = ../db

####日志文件位置####
[log]
logfile = ../log/server.log
usermgr_log = ../log/usermgr.log

####上传文件存放位置####
[upload]
upload_dir= ../user_files

####用户信息存放位置####
[db]
db_dir = ../db

main.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socketserver,os
from usermanagement import useropr
from server import MyTCPHandler

info = ‘‘‘
        1、启动服务器
        2、进入用户管理
        按q退出
‘‘‘

if __name__ == ‘__main__‘:
    while True:
        print(info)
        choice = input(‘>>>:‘)
        if choice == ‘q‘:
            exit()
        elif choice == ‘1‘:
            ip, port = ‘0.0.0.0‘, 9999
            server = socketserver.ThreadingTCPServer((ip, port), MyTCPHandler)
            server.serve_forever()
        elif choice == ‘2‘:
            useropr.interactive()
        else:continue

usermanagement

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#filename:usermanagement.py
import os,hashlib,time,pickle,shutil,configparser,logging

####读取配置文件####
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_file = os.path.join(base_dir, ‘conf/server.conf‘)
cf = configparser.ConfigParser()
cf.read(config_file)
####设定日志目录####
if os.path.exists(cf.get(‘log‘,‘usermgr_log‘)):
    logfile = cf.get(‘log‘, ‘usermgr_log‘)
else:
    logfile = os.path.join(base_dir,‘log/usermgr.log‘)
####设定用户上传文件目录,这边用于创建用户家目录使用####
if os.path.exists(cf.get(‘upload‘,‘upload_dir‘)):
    file_dir = cf.get(‘upload‘,‘upload_dir‘)
else:
    file_dir = os.path.join(base_dir,‘user_files‘)
####设定用户信息存储位置####
if os.path.exists(cf.get(‘db‘,‘db_dir‘)):
    db_path = cf.get(‘db‘,‘db_dir‘)
else:
    db_path = os.path.join(base_dir,‘db‘)

def hashmd5(*args):             ####用于加密密码信息
    m = hashlib.md5()
    m.update(str(*args).encode())
    return m.hexdigest()

class useropr(object):
    def __init__(self,user_name,passwd = ‘123456‘,phone_number=‘‘):
        self.user_name = user_name
        self.id = time.strftime("%Y%m%d%H%M%S", time.localtime())
        self.phone_number = phone_number
        self.passwd = passwd
        self.space_size = 104857600         ####初始分配100MB存储空间
        self.member_level = 1               ####会员等级,初始为1,普通会员

    @staticmethod                       ####使用静态方法,可以直接用类命调用,如user.search_user(username),否则需要实例化一个对象后才能调用
    def query_user(user_name):        ####查询用户
        db_filelist=os.listdir(db_path)
        #print(db_filelist)
        dict={}
        for filename in db_filelist:
            with open(os.path.join(db_path,filename),‘rb‘) as f:
                content=pickle.load(f)
                #print(filename,content)    ####开启会打印出所有用户信息
                if content[‘username‘] == user_name:
                    #print(filename, content)
                    dict={‘filename‘:filename,‘content‘:content}
                    return dict

    def save_userinfo(self):                    ####保存用户信息
        query_result = self.query_user(self.user_name)      ####检查是否已存在同名用户,如果没有查询结果应该为None
        if query_result == None:
            user_info = {
                ‘username‘:self.user_name,
                ‘id‘:self.id,
                ‘phonenumber‘:self.phone_number,
                ‘passwd‘:hashmd5(self.passwd),
                ‘spacesize‘:self.space_size,
                ‘level‘:self.member_level
            }
            with open(os.path.join(db_path,self.id),‘wb‘) as f:
                pickle.dump(user_info,f)
                print(‘用户信息保存完毕‘)
                try:                                                    ####创建用户家目录
                    os.mkdir(os.path.join(file_dir, self.user_name))
                    print(‘用户目录创建成功!‘)
                except Exception as e:
                    print(‘用户目录创建失败,‘,e)

        else:
            print(‘用户名重复,信息未保存‘)

    @staticmethod
    def change_info(user_name,**kwargs):           ####修改信息
        query_result = useropr.query_user(user_name)  ####用于检测用户是否存在,不存在不处理
        if query_result != None:
            userinfo_filename = query_result[‘filename‘]
            user_info = query_result[‘content‘]
            print(‘before update:‘,user_info)
            for key in kwargs:
                if key in (‘username‘,‘id‘):            ####用户名和ID不可更改
                    print(key,‘项不可更改‘)
                elif key in (‘passwd‘,‘phonenumber‘,‘spacesize‘,‘level‘):         ####允许修改的键值
                    if key == ‘passwd‘:
                        user_info[key] = hashmd5(kwargs[key])  ####加密密码保存
                    else:
                        user_info[key] = kwargs[key]
                    with open(os.path.join(db_path, userinfo_filename), ‘wb‘) as f:
                        pickle.dump(user_info, f)
                        print(key,‘项用户信息变更保存完毕‘)
                else:
                    print(‘输入信息错误,‘,key,‘项不存在‘)
            print(‘after update:‘,user_info)
        else:
            print(‘用户不存在‘)

    @staticmethod
    def delete_user(user_name):              ####删除用户
        query_result = useropr.query_user(user_name)  ####用于检测用户是否存在,不存在不处理
        if query_result != None:
            userinfo_filename = query_result[‘filename‘]
            userfile_path=os.path.join(db_path, userinfo_filename)
            os.remove(userfile_path)
            query_result_again = useropr.query_user(user_name)
            if query_result_again == None:
                print(‘用户DB文件删除成功‘)
                try:
                    shutil.rmtree(os.path.join(file_dir,user_name))
                    print(‘用户家目录删除成功‘)
                except Exception as e:
                    print(‘用户家目录删除失败:‘,e)
            else:
                print(‘用户DB文件删除失败‘)

        else:
            print(‘用户不存在或者已经被删除‘)

    @staticmethod
    def query_alluser():        ####查询所有用户信息,用于调试使用
        db_filelist=os.listdir(db_path)
        for filename in db_filelist:
            with open(os.path.join(db_path,filename),‘rb‘) as f:
                content=pickle.load(f)
                print(filename,content)

    @staticmethod
    def interactive():
        ‘‘‘使用说明:
        新增用户请输入类似: a=useropr(username,passwd)
                            a.save_userinfo()
        查询用户请输入:useropr.query_user(username)
        更改用户信息请输入:useropr.change_info(username,id=123,level=1,passwd=123,phonenumber=123),其中字典部分为可选项
        用户删除请输入:useropr.delete_user(username)
        ‘‘‘
        info=‘‘‘
        1、新增用户
        2、查询用户
        3、修改用户
        4、删除用户
        退出请按q
        ‘‘‘

        #useropr.query_alluser()        ####查询所有用户信息,调试用

        while True:
            print(info)
            choice = input(‘请输入你的选择:‘).strip()
            #print(‘operation choice: %s‘ % choice)
            if choice == ‘q‘:
                exit()
            else:
                username = input(‘请输入用户名:‘).strip()
                #print(‘username: %s‘ % username)
                if username == ‘‘:
                    print(‘用户不能为空‘)
                    continue
                elif choice == ‘1‘:
                    passwd = input(‘请输入密码:‘)
                    new_user = useropr(username, passwd)
                    new_user.save_userinfo()

                elif choice == ‘2‘:
                    print(useropr.query_user(username))

                elif choice == ‘3‘:
                    update_item = input(‘请输入要修改的项目,例如:level,passwd,phonenumber:‘)
                    print(‘update item: %s‘ % update_item)
                    update_value = input(‘请输入要修改的项目新值:‘)
                    useropr.change_info(username,**{update_item:update_value})      #### ‘**{}’ 不加**系统无法识别为字典。不能直接使用update_item=update_value,update_item会直接被当成key值,而不是其中的变量。

                elif choice == ‘4‘:
                    useropr.delete_user(username)

                else:
                    print(‘输入错误‘)
                    continue

if __name__ == ‘__main__‘:
    useropr.interactive()

server.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#filename:server.py
import socketserver,json,os,sys,time,shutil,configparser,logging
from usermanagement import useropr

####读取配置文件####
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_file = os.path.join(base_dir, ‘conf/server.conf‘)
cf = configparser.ConfigParser()
cf.read(config_file)
####设定日志目录####
if os.path.exists(cf.get(‘log‘,‘logfile‘)):
    logfile = cf.get(‘log‘, ‘logfile‘)
else:
    logfile = os.path.join(base_dir,‘log/server.log‘)
####设定用户上传文件目录####
if os.path.exists(cf.get(‘upload‘,‘upload_dir‘)):
    file_dir = cf.get(‘upload‘,‘upload_dir‘)
else:
    file_dir = os.path.join(base_dir,‘user_files‘)

####设置日志格式###
logging.basicConfig(level=logging.INFO,
                    format=‘%(asctime)s %(levelname)s %(message)s‘,
                    datefmt=‘%Y-%m-%d    %H:%M:%S‘,
                    filename=logfile,
                    filemode=‘a+‘)

def TimeStampToTime(timestamp):         ####输入timestamp格式化输出时间,输出格式如:2017-09-16 16:32:35
    timeStruct = time.localtime(timestamp)
    return time.strftime(‘%Y-%m-%d %H:%M:%S‘,timeStruct)

def ProcessBar(part,total):             ####进度条模块,运行会导致程序变慢
    if total !=0:
        i=round(part*100/total)
        sys.stdout.write(‘[‘ + ‘>‘ * i + ‘-‘ * (100 - i)  + ‘]‘ + str(i) + ‘%‘ + ‘ ‘ * 3 + str(part) + ‘/‘ + str(total) + ‘\r‘)
        sys.stdout.flush()
        # if part == total:
        #     print()

class MyTCPHandler(socketserver.BaseRequestHandler):
    def put(self,*args):    ####接收客户端文件
        #self.request.send(b‘server have been ready to receive‘)    ####发送ACK
        cmd_dict = args[0]
        filename = os.path.basename(cmd_dict[‘filename‘])    ####传输进来的文件名可能带有路径,将路径去掉
        filesize = cmd_dict[‘filesize‘]
        filemd5 = cmd_dict[‘filemd5‘]
        override = cmd_dict[‘override‘]
        receive_size = 0
        file_path = os.path.join(self.position,filename)
        if override != ‘True‘ and os.path.exists(file_path):                        ####检测文件是否已经存在
            self.request.send(b‘file have exits, do nothing!‘)
        else:
            if os.path.isfile(file_path):       ####如果文件已经存在,先删除,再计算磁盘空间大小
                os.remove(file_path)
            current_size = self.du()                ####调用du查看用户磁盘空间大小,但是du命令的最后会发送一个结果信息给client,会和前面和后面的信息粘包,需要注意
            self.request.recv(1024)                 ####接收客户端ack信号,防止粘包,代号:P01
            print(self.user_spacesize,current_size,filesize)
            if self.user_spacesize >= current_size + filesize:
                self.request.send(b‘begin‘)        ####发送开始传输信号
                fk = open(file_path,‘wb‘)
                while filesize > receive_size:
                    if filesize - receive_size > 1024:
                        size = 1024
                    else:
                        size = filesize - receive_size
                    data = self.request.recv(size)
                    fk.write(data)
                    receive_size += len(data)
                    #print(receive_size,len(data))   ####打印每次接收的数据
                    #ProcessBar(receive_size, filesize)  ####服务端进度条,不需要可以注释掉

                fk.close()
                receive_filemd5 = os.popen(‘md5sum %s‘ % file_path).read().split()[0]
                print(‘\r\n‘,file_path,‘md5:‘,receive_filemd5,‘原文件md5:‘,filemd5)
                if receive_filemd5 == filemd5:
                    self.request.send(b‘file received successfully!‘)
                else:
                    self.request.send(b‘Error, file received have problems!‘)
            else:
                self.request.send(b‘Error, disk space do not enough! Nothing done! Total: %d, current: %d, rest:%d, filesize:%d‘ % (self.user_spacesize,current_size,self.user_spacesize-current_size,filesize))

    def get(self,*args):    ####发送给客户端文件
        #print(‘get receive the cmd‘,args[0])
        filename = args[0][‘filename‘]
        print(filename)
        #self.request.send(b‘server have been ready to send‘)  ####发送ACK
        file_path = os.path.join(self.position, filename)
        if os.path.isfile(file_path):
            filesize = os.path.getsize(file_path)
            filemd5 = os.popen(‘md5sum %s‘ % file_path).read().split()[0]  ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
            msg = {
                ‘action‘: ‘get‘,
                ‘filename‘: filename,
                ‘filesize‘: filesize,
                ‘filemd5‘: filemd5,
                ‘override‘: ‘True‘
            }
            print(msg)
            self.request.send(json.dumps(msg).encode(‘utf-8‘))

            ‘‘‘接下来发送文件给客户端‘‘‘
            self.request.recv(1024)  ####接收ACK信号,下一步发送文件
            fk = open(file_path, ‘rb‘)
            send_size = 0
            for line in fk:
                send_size += len(line)
                self.request.send(line)
                #ProcessBar(send_size, filesize)     ####服务端进度条,不需要可以注释掉
            else:
                print(‘文件传输完毕‘)
                fk.close()

        else:
            print(file_path,‘文件未找到‘)
            self.request.send(json.dumps(‘Filenotfound‘).encode(‘utf-8‘))

    def pwd(self,*args):
        current_position = self.position
        result = current_position.replace(file_dir,‘‘)         ####截断目录信息,使用户只能看到自己的家目录信息
        self.request.send(json.dumps(result).encode(‘utf-8‘))

    def ls(self,*args):  ####列出当前目录下的所有文件信息,类型,字节数,生成时间。
        result = [‘%-20s%-7s%-10s%-23s‘ % (‘filename‘, ‘type‘, ‘bytes‘, ‘creationtime‘)]  ####信息标题
        for f in os.listdir(self.position):
            type = ‘unknown‘
            f_abspath = os.path.join(self.position, f)  ####给出文件的绝对路径,不然程序会找不到文件
            if os.path.isdir(f_abspath):
                type = ‘d‘
            elif os.path.isfile(f_abspath):
                type = ‘f‘
            result.append(‘%-20s%-7s%-10s%-23s‘ % (f, type, os.path.getsize(f_abspath), TimeStampToTime(os.path.getctime(f_abspath))))
        self.request.send(json.dumps(result).encode(‘utf-8‘))

    def du(self,*args):
        ‘‘‘统计纯文件和目录占用空间大小,结果小于在OS上使用du -s查询,因为有一些(例如‘.‘,‘..‘)隐藏文件未包含在内‘‘‘
        totalsize = 0
        if os.path.isdir(self.position):
            dirsize, filesize = 0, 0
            for root, dirs, files in os.walk(self.position):
                for d_item in dirs:  ####计算目录占用空间,Linux中每个目录占用4096bytes,实际上也可以按这个值来相加
                    if d_item != ‘‘:
                        dirsize += os.path.getsize(os.path.join(root, d_item))
                for f_item in files:  ####计算文件占用空间
                    if f_item != ‘‘:
                        filesize += os.path.getsize(os.path.join(root, f_item))
            totalsize = dirsize + filesize
            result=‘current directory total sizes: %d‘ % totalsize
        else:
            result=‘Error,%s is not path ,or path does not exist!‘ % self.position
        self.request.send(json.dumps(result).encode(‘utf-8‘))
        return totalsize

    def cd(self,*args):
        print(*args)
        cmd_dict = args[0]
        error_tag=False
        ‘‘‘判断目录信息‘‘‘
        if cmd_dict[‘dir‘] == ‘‘:
            self.position = user_homedir
        elif cmd_dict[‘dir‘] == ‘.‘ or cmd_dict[‘dir‘] == ‘/‘ or ‘//‘ in cmd_dict[‘dir‘]:            ####‘.‘,‘/‘,‘//‘,‘///+‘匹配
            pass
        elif cmd_dict[‘dir‘] == ‘..‘:
            if user_homedir != self.position and user_homedir in self.position:   ####当前目录不是家目录,并且当前目录是家目录下的子目录
                self.position = os.path.dirname(self.position)
        elif ‘.‘ not in cmd_dict[‘dir‘] and os.path.isdir(os.path.join(self.position, cmd_dict[‘dir‘])):       ####‘.‘ not in cmd_dict[‘dir‘] 防止../..输入
            self.position = os.path.join(self.position, cmd_dict[‘dir‘])
        else:
            error_tag=True
        ‘‘‘发送结果‘‘‘
        if error_tag:
            result = ‘Error,%s is not path here, or path does not exist!‘ % cmd_dict[‘dir‘]
            self.request.send(json.dumps(result).encode(‘utf-8‘))
        else:
            self.pwd()

    def mkdir(self,*args):  ####创建目录
        try:
            dirname = args[0][‘dirname‘]
            if dirname.isalnum():           ####判断文件是否只有数字和字母
                if os.path.exists(os.path.join(self.position, dirname)):
                    result = ‘%s have existed‘ % dirname
                else:
                    os.mkdir(os.path.join(self.position, dirname))
                    result = ‘%s created succes‘ % dirname
            else:
                result=‘Illegal character %s, dirname can only by string and num here.‘ % dirname
        except TypeError:
            result = ‘please input dirname‘
        self.request.send(json.dumps(result).encode(‘utf-8‘))

    def rm(self,*args):  ####删除文件
        filename = args[0][‘filename‘]
        confirm =args[0][‘confirm‘]
        file_abspath = os.path.join(self.position, filename)
        if os.path.isfile(file_abspath):
            if confirm == True:
                os.remove(file_abspath)
                result=‘%s have been delete.‘ % filename
            else:
                result=‘Not file deleted‘
        elif os.path.isdir(file_abspath):
            result=‘%s is a dir, plsese using rmdir‘ % filename
        else:
            result=‘File %s not exist!‘ % filename
        self.request.send(json.dumps(result).encode(‘utf-8‘))

    def rmdir(self,*args):  ###删除目录
        dirname = args[0][‘dirname‘]
        confirm =args[0][‘confirm‘]
        file_abspath = os.path.join(self.position, dirname)
        if ‘.‘ in dirname or  ‘/‘ in dirname :          ####不能跨目录删除
            result=‘should not rmdir %s this way‘ % dirname
        elif os.path.isdir(file_abspath):
            if confirm == True:
                shutil.rmtree(file_abspath)
                result=‘%s have been delete.‘ % dirname
            else:
                result=‘Not file deleted‘
        elif os.path.isfile(file_abspath):
            result=‘%s is a file, not directory deleted‘ % dirname
        else:
            result=‘directory %s not exist!‘ % dirname
        self.request.send(json.dumps(result).encode(‘utf-8‘))

    def auth(self):
        self.data = json.loads(self.request.recv(1024).decode(‘utf-8‘))
        print(self.data)
        recv_username = self.data[‘username‘]
        recv_passwd = self.data[‘passwd‘]
        query_result=useropr.query_user(recv_username)
        print(query_result)
        if query_result == None:
            self.request.send(b‘user does not exits‘)
        elif query_result[‘content‘][‘passwd‘] == recv_passwd:
            self.request.send(b‘ok‘)
            return query_result       ####返回查询结果
        elif query_result[‘content‘][‘passwd‘] != recv_passwd:
            self.request.send(b‘password error‘)
        else:
            self.request.send(b‘unknown error‘)

    def handle(self):       ####处理类,调用以上方法
        #self.position = file_dir
        #print(self.position)
        auth_tag = False
        while auth_tag != True:
            auth_result = self.auth() ####用户认证,如果通过,返回用户名,不通过为None
            print(‘the authentication result is:‘,auth_result)
            if auth_result != None:
                self.username = auth_result[‘content‘][‘username‘]
                self.user_spacesize = auth_result[‘content‘][‘spacesize‘]
                auth_tag = True
        print(self.username,self.user_spacesize)
        user_homedir = os.path.join(file_dir,self.username)
        if os.path.isdir(user_homedir):
            self.position = user_homedir        ####定锚,用户家目录
            print(self.position)
            while True:
                print(‘当前连接:‘,self.client_address)
                self.data = self.request.recv(1024).strip()
                print(self.data)
                logging.info(self.client_address)
                if len(self.data) == 0 :
                    print(‘客户端断开连接‘)
                    break           ####检查发送来的命令是否为空
                cmd_dict = json.loads(self.data.decode(‘utf-8‘))
                action = cmd_dict[‘action‘]
                logging.info(cmd_dict)
                if hasattr(self,action):
                    func = getattr(self,action)
                    func(cmd_dict)
                else:
                    print(‘未支持指令:‘,action)
                logging.info(‘current directory:%s‘ % self.position)

if __name__ == ‘__main__‘:
    ip,port=‘0.0.0.0‘,9999
    server = socketserver.ThreadingTCPServer((ip,port),MyTCPHandler)
    server.serve_forever()

2、客户端

client.conf

####用户端配置文件####
[DEFAULT]
logfile = ../log/client.log
download_dir= ../temp

####日志文件位置####
[log]
logfile = ../log/client.log

####下载文件存放位置####
[download]
download_dir= ../temp

main.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import configparser,os
from client import FtpClient

if __name__ == ‘__main__‘:
    ftp = FtpClient()
    ftp.connect(‘127.0.0.1‘,9999)
    auth_tag=False
    while auth_tag != True:
        auth_tag=ftp.auth()
    ftp.interactive()

client.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#filename:client.py
import socket,json,os,sys,hashlib,getpass,logging,configparser

####读取配置文件####
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_file = os.path.join(base_dir, ‘conf/client.conf‘)
cf = configparser.ConfigParser()
cf.read(config_file)
####设定日志目录####
if os.path.exists(cf.get(‘log‘,‘logfile‘)):
    logfile = cf.get(‘log‘, ‘logfile‘)
else:
    logfile = os.path.join(base_dir,‘log/client.log‘)
####设定下载目录####
if os.path.exists(cf.get(‘download‘,‘download_dir‘)):
    download_dir = cf.get(‘download‘,‘download_dir‘)
else:
    download_dir = os.path.join(base_dir,‘temp‘)

####设置日志格式###
logging.basicConfig(level=logging.INFO,
                    format=‘%(asctime)s %(levelname)s %(message)s‘,
                    datefmt=‘%Y-%m-%d %H:%M:%S‘,
                    filename=logfile,
                    filemode=‘a+‘)

def hashmd5(*args):             ####用于加密密码信息
    m = hashlib.md5()
    m.update(str(*args).encode())
    return m.hexdigest()

def ProcessBar(part,total):         ####进度条模块
    if total !=0:
        i=round(part*100/total)
        sys.stdout.write(‘[‘ + ‘>‘ * i + ‘-‘ * (100 - i)  + ‘]‘ + str(i) + ‘%‘ + ‘ ‘ * 3 + str(part) + ‘/‘ + str(total) + ‘\r‘)
        sys.stdout.flush()

class FtpClient(object):
    def __init__(self):
        self.client = socket.socket()

    def connect(self,ip,port):
        self.client.connect((ip,port))

    def exec_linux_cmd(self, dict):             ####用于后面调用linux命令
        logging.info(dict)                      ####将发送给服务端的命令保存到日志中
        self.client.send(json.dumps(dict).encode(‘utf-8‘))
        server_response = json.loads(self.client.recv(4096).decode(‘utf-8‘))
        if isinstance(server_response,list):
            for i in server_response:
                print(i)
        else:
            print(server_response)

    def help(self):
        info = ‘‘‘
        仅支持如下命令:
        ls
        du
        pwd
        cd dirname/cd ./cd ..
        mkdir dirname
        rm  filename
        rmdir dirname
        put filename
        get filename
        ‘‘‘
        print(info)
    def interactive(self):
        while True:
            self.pwd()      ####打印当前目录位置
            cmd = input(‘>>>:‘).strip()
            if len(cmd) == 0: continue
            action = cmd.split()[0]
            if hasattr(self,action):
                func=getattr(self,action)
                func(cmd)
            else:
                self.help()

    def put(self,*args):    ####上传文件
        cmd = args[0].split()
        override = cmd[-1]      ####override:是否覆盖参数,放在最后一位
        if override != ‘True‘:
            override = ‘False‘
        #print(cmd,override)
        if len(cmd)>1:
            filename = cmd[1]
            if os.path.isfile(filename):
                filesize = os.path.getsize(filename)
                filemd5 = os.popen(‘md5sum %s‘ % filename).read().split()[0]    ####直接调用系统命令取得MD5值,如果使用hashlib,需要写open打开文件-》read读取文件(可能文件大会很耗时)-》m.update计算三部,代码量更多,效率也低
                msg = {
                    ‘action‘:‘put‘,
                    ‘filename‘:filename,
                    ‘filesize‘:filesize,
                    ‘filemd5‘:filemd5,
                    ‘override‘:override           ####True ,or False
                }
                logging.info(msg)
                self.client.send(json.dumps(msg).encode(‘utf-8‘))
                server_response = self.client.recv(1024)            ####等待服务器确认信号,防止粘包
                logging.info(server_response)
                if server_response == b‘file have exits, do nothing!‘:
                    override_tag=input(‘文件已存在,要覆盖文件请输入yes >>>:‘)
                    if override_tag == ‘yes‘:
                        self.put(‘put %s True‘ % filename)
                    else:
                        print(‘文件未上传‘)

                else:
                    self.client.send(b‘client have ready to send‘)  ####发送确认信号,防止粘包,代号:P01
                    server_response = self.client.recv(1024).decode(‘utf-8‘)
                    print(server_response)       ####注意:用于打印服务器反馈信息,例如磁盘空间不足信息,不能取消
                    if server_response == ‘begin‘:
                        fk = open(filename,‘rb‘)
                        send_size = 0
                        for line in fk:
                            #print(len(line))
                            send_size += len(line)
                            self.client.send(line)
                            ProcessBar(send_size, filesize)
                        else:
                            print(‘\r\n‘,‘文件传输完毕‘)
                            fk.close()
                            server_response = self.client.recv(1024).decode(‘utf-8‘)
                            print(server_response)

            else:
                print(‘文件不存在‘)
        else:
            print(‘请输入文件名‘)

    def get(self,*args):    ####下载文件
        cmd = args[0].split()
        #print(args[0],cmd)
        if len(cmd)>1:
            filename = cmd[1]
            msg = {
                ‘action‘: ‘get‘,
                ‘filename‘: filename,
                ‘filesize‘: 0,
                ‘filemd5‘: ‘‘,
                ‘override‘: ‘True‘
            }
            logging.info(msg)
            self.client.send(json.dumps(msg).encode(‘utf-8‘))
            server_response=json.loads(self.client.recv(1024).decode(‘utf-8‘))
            logging.info(server_response)
            if server_response == ‘Filenotfound‘:
                print(‘File no found!‘)
            else:
                print(server_response)
                self.client.send(b‘client have been ready to receive‘)  ####发送信号,防止粘包
                filesize = server_response[‘filesize‘]
                filemd5 = server_response[‘filemd5‘]
                receive_size = 0
                filepath = os.path.join(download_dir, filename)
                fk = open(os.path.join(download_dir,filepath), ‘wb‘)
                while filesize > receive_size:
                    if filesize - receive_size > 1024:
                        size = 1024
                    else:
                        size = filesize - receive_size
                    data = self.client.recv(size)
                    fk.write(data)
                    receive_size += len(data)
                    #print(receive_size, len(data))          ####打印数据流情况
                    ProcessBar(receive_size, filesize)      ####打印进度条

                fk.close()
                receive_filemd5 = os.popen(‘md5sum %s‘ % filepath).read().split()[0]
                print(‘\r\n‘,filename, ‘md5:‘, receive_filemd5, ‘原文件md5:‘, filemd5)
                if receive_filemd5 == filemd5:
                    print(‘文件接收完成!‘)
                else:
                    print(‘Error,文件接收异常!‘)
        else:
            print(‘请输入文件名‘)

    def pwd(self,*args):    ####查看用户目录
        msg = {
            ‘action‘:‘pwd‘,
        }
        self.exec_linux_cmd(msg)

    def ls(self,*args):     ####查看文件信息
        msg = {
            ‘action‘:‘ls‘,
        }
        self.exec_linux_cmd(msg)

    def du(self,*args):     ####查看当前目录大小
        msg = {
            ‘action‘:‘du‘,
        }
        self.exec_linux_cmd(msg)

    def cd(self,*args):     ####切换目录
        try:                    ####如果是直接输入cd,dirname=‘‘
            dirname = args[0].split()[1]
        except IndexError:
            dirname = ‘‘
        msg = {
            ‘action‘: ‘cd‘,
            ‘dir‘:dirname
        }
        self.exec_linux_cmd(msg)

    def mkdir(self,*args):  ####生成目录
        try:                    ####如果是直接输入rm,跳出
            dirname = args[0].split()[1]
            msg = {
                ‘action‘: ‘mkdir‘,
                ‘dirname‘: dirname,
            }
            self.exec_linux_cmd(msg)
        except IndexError:
            print(‘Not dirname input, do nothing.‘)
            pass

    def rm(self,*args):     ####删除文件
        try:                    ####如果是直接输入rm,跳出
            filename = args[0].split()[1]
            msg = {
                ‘action‘: ‘rm‘,
                ‘filename‘: filename,
                ‘confirm‘:True          ####确认是否直接删除标志
            }
            self.exec_linux_cmd(msg)
        except IndexError:
            print(‘Not filename input, do nothing.‘)
            pass

    def rmdir(self,*args):
        try:                    ####如果是直接输入rm,跳出
            dirname = args[0].split()[1]
            msg = {
                ‘action‘: ‘rmdir‘,
                ‘dirname‘: dirname,
                ‘confirm‘:True          ####确认是否直接删除标志
            }
            self.exec_linux_cmd(msg)
        except IndexError:
            print(‘Not dirname input, do nothing.‘)
            pass

    def auth(self):
        user_name = input(‘请输入用户名>>>:‘).strip()
        passwd = getpass.getpass(‘请输入密码>>>:‘).strip()           ####在linux上输入密码不显示
        msg={
            ‘username‘:user_name,
            ‘passwd‘:hashmd5(passwd)
        }
        self.client.send(json.dumps(msg).encode(‘utf-8‘))
        server_response = self.client.recv(1024).decode(‘utf-8‘)
        if server_response == ‘ok‘:
            print(‘认证通过!‘)
            return True
        else:
            print(server_response)
            return False

if __name__ == ‘__main__‘:
    ftp = FtpClient()
    ftp.connect(‘127.0.0.1‘,9999)
    auth_tag=False
    while auth_tag != True:
        auth_tag=ftp.auth()
    ftp.interactive()

注:配置文件中的中文注释,可能会使程序在启动时报出ASCII decode error,可以去掉。

时间: 2024-11-01 15:38:05

python 多用户在线的FTP程序的相关文章

Python开发程序:支持多用户在线的FTP程序

作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目录下文件 允许上传和下载文件,保证文件一致性 文件传输过程中显示进度条 附加功能:支持文件的断点续传(仅下载) 程序 1.最最重要的readme: ### 作者介绍: * author:lzl ### 博客地址: * http://www.cnblogs.c

开发一个支持多用户在线的FTP程序

一,项目题目:开发一个支持多用户在线的FTP程序 二,项目要求: 1.用户加密认证 2.允许同时多用户登录 3.每个用户有自己的家目录 ,且只能访问自己的家目录 4.对用户进行磁盘配额,每个用户的可用空间不同 5.允许用户在ftp server上随意切换目录 6.允许用户查看当前目录下文件 7.允许上传和下载文件,保证文件一致性(md5) 8.文件传输过程中显示进度条 9.附加功能:支持文件的断点续传 三,注意事项: 基本要求. 完成1,2,3,5,6,7,8 实力选手. 完成 上条 及需求4

【小程序】支持多用户在线的FTP程序

功能:1.用户加密认证: 2.允许同时多用户登陆: 3.每个用户有自己的家目录,并且只能访问在自己的家目录: 4.对用户进行磁盘配额,每个用户的可用空间不同: 5.允许用户在ftp server上随意切换目录: 6.允许用户查看当前目录上下文: 7.允许用户上传和下载文件,保证文件的一致性 8.文件传输过程中显示进度条: 客户端: # Author:q1.ang import socket,os,json import hashlib import sys class FtpClient(obj

支持多用户在线的Ftp程序

一.要求 二.思路 三.代码 服务器端: # #!/usr/bin/env python # # -*- coding: utf-8 -*- import sys import time import socket import hashlib import pickle import subprocess import socketserver class Myserver(socketserver.BaseRequestHandler): def recv_file(self): conn=

python 开发一个支持多用户在线的FTP

### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目录下文件 允许上传和下载文件,保证文件一致性 文件传输过程中显示进度条 附加功能

python作业:高级FTP程序

要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目录下文件 允许上传和下载文件,保证文件一致性 文件传输过程中显示进度条 附加功能:支持文件的断点续传 程序流程图: 程序Readme: MiniFTP主要是由三部分组成:服务器端.客户端.管理端.本文主要就是介绍以上三部分内容. 系统初始配置 管理员账号 用户名:admin 密码:password

Python:socket实现ftp程序

刚开始学习socket编程,还不是特熟练,码了好长时间,中间遇到许多问题,记录一下用socketserver写ftp server端: 1 #!/usr/bin/env python 2 3 import socketserver,os 4 5 class FTP(socketserver.BaseRequestHandler): 6 def handle(self): 7 def clientsend(filename): 8 f = open('/ftp/%s' %filename,'w')

Python实现在线翻译的程序

import urllib.request import urllib.parse import json content=input("想要翻译的英文:") url="http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=http://www.youdao.com/" data={} data["

FTP程序

需求:开发一个支持多用户同时在线的FTP程序 要求:1.用户加密认证2.允许同时多用户登录(用到并发编程的知识,选做)3.每个用户有自己的家目录,且只能访问自己的家目录4.对用户进行磁盘配额,每个用户的可用空间不同(选做)5.允许用户在ftp server上随意切换目录6.允许用户查看当前目录下的文件7.允许上传和下载文件,并保证文件的一致性8.文件传输过程中显示进度条9.附加:支持文件的断点续传(选做)开发的程序需符合PEP8开发规范,及专业的生产软件设计规范,包括目录.代码命名.功能接口等