Python之路【第十五篇】開發FTP多線程程序

要求: 1.用戶加密認證 2.容許同時多用戶登陸 3.每一個用戶有本身的家目錄,且只能訪問本身的家目錄 4.對用戶進行磁盤配額,每一個用戶的可用空間不一樣 5.容許用戶在ftp server上隨意切換目錄 6.容許用戶查看當前目錄下的文件 7.容許上傳和下載文件,並保證文件的一致性md5 8.文件傳輸過程當中顯示進度條 9.支持文件的斷點續傳
使用:
1.啓動ftp_server.py
2.建立用戶,輸入:用戶名(默認密碼是zhurui)
3.啓動FTP服務器
4.啓動客戶端ftp_client.py
5.輸入用戶名和密碼:alex zhurui | william zhurui
6.與服務器server交互:

    

 

server服務端

bin下的文件 

ftp_server.pylinux

#_*_ coding:utf-8 _*_ #Author :simon

import os import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core.main import Manager if __name__ == '__main__': Manager().run()

conf下的文件

accounts.ini(這個能夠在執行中建立)git

[william] password = 39da56d2e7a994d38b9aaf329640fc6e homedir = home/william quota = 10 [zhurui] password = 39da56d2e7a994d38b9aaf329640fc6e homedir = home/zhurui quota = 10 [simon] password = 39da56d2e7a994d38b9aaf329640fc6e homedir = home/simon quota = 10

settings.pyshell

#_*_ coding:utf-8 _*_ # Author:Simon # Datetime:2019/8/14 11:00 # Software:PyCharm

import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ACCOUNTS_FILE = os.path.join(BASE_DIR, 'conf', 'accounts.ini') HOST = '127.0.0.1' PORT = 8080 MAX_CONCURRENT_COUNT = 10

core下的文件

main.pywindows

# _*_ coding:utf-8 _*_ #Author:Simon

from core.user_handle import UserHandle from core.server import Ftpserver class Manager(): def __init__(self): pass
    def start_ftp(self): '''啓動ftp_server端''' server = Ftpserver() server.run() server.close() def create_user(self): '''建立用戶''' username = input('請輸入要建立的用戶>:').strip() UserHandle(username).add_user() def quit_func(self): quit('get out...') def run(self): msg = '''\033[31;0m 一、啓動ftp服務器 二、建立用戶 三、退出\033[0m\n ''' msg_dic = {'1': 'start_ftp', '2': 'create_user', '3': 'quit_func'} while True: print(msg) num = input('請輸入數字num>>>>:').strip() if num in msg_dic: getattr(self,msg_dic[num])() else: print('\033[1;31m請從新選擇\033[0m')

server.py服務器

#-*- coding:utf-8 -*- # Author:Simon # Datetime:2019/8/13 21:02 # Software:PyCharm

import os import socket import struct import pickle import hashlib import subprocess import queue from conf import settings # from core.user_handle import UserHandle
from core.user_handle import UserHandle from threading import Thread, Lock class Ftpserver(): MAX_SOCKET_LISTEN = 5 MAX_RECV_SIZE = 8192

    def __init__(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.bind((settings.HOST, settings.PORT)) self.socket.listen(self.MAX_SOCKET_LISTEN) self.q = queue.Queue(settings.MAX_CONCURRENT_COUNT) # 能夠配置最大併發數

    def server_accept(self): '''等待client鏈接'''
        print('starting...') while True: self.conn,self.client_addr = self.socket.accept() print('客戶端地址:', self.client_addr) #pool.submit(self.get_recv, self.conn)
        #self.server_accept.close()
            try: # t = Thread(target=self.server_handle, args=(self.conn, )) #報這個錯(TypeError: server_handle() takes 1 positional argument but 2 were given)
                t = Thread(target=self.server_handle(), args=(self.conn, )) self.q.put(t) t.start() except Exception as e: print(e) self.conn.close() self.q.get() def get_recv(self): '''接收client發來的數據'''
        return pickle.loads(self.conn.recv(self.MAX_RECV_SIZE)) def auth(self): '''處理用戶的認證請求 一、根據username讀取accounts.ini文件,password相比,判斷用戶是否存在 二、將程序運行的目錄從bin/ftp_server.py修改到用戶home/alice,方便以後查詢ls 三、給client返回用戶的詳細信息 '''

        while True: user_dic = self.get_recv() username = user_dic.get('username') user_handle = UserHandle(username) user_data = user_handle.judge_user() # 判斷用戶是否存在,返回列表
            # 如[('password','202cb962ac59075b964b07152d234b70'),('homedir','home/alex'),('quota','100')]
            if user_data: if user_data[0][1] == hashlib.md5(user_dic.get('password').encode('utf-8')).hexdigest(): # 密碼也相同
                    self.conn.send(struct.pack('i', 1))  #登陸成功返回
                    self.username = username self.homedir_path = '%s %s %s' %(settings.BASE_DIR, 'home', self.username) os.chdir(self.homedir_path) #將程序運行的目錄名修改到用戶home目錄下
                    self.quota_bytes = int(user_data[2][1]) * 1024 * 1024 #將用戶配額大小從M改到字節
                    user_info_dic = { 'username': username, 'homedir': user_data[1][1], 'quota': user_data[2][1] } self.conn.send(pickle.dumps(user_info_dic)) #用戶的詳細信息發送到客戶端
                    return True else: self.conn.send(struct.pack('i', 0)) else: self.conn.send(struct.pack('i', 0)) def readfile(self): '''讀取文件,獲得文件內容的bytes型''' with open(self.filepath, 'rb') as f: filedata = f.read() return filedata def getfile_md5(self): '''對文件內容md5'''
        return hashlib.md5(self.readfile()).hexdigest() def get(self): '''從server下載文件到client '''
        if len(self.cmds) > 1: filename = self.cmds[1] filepath = os.path.join(os.getcwd(),filename)  #os.getcwd()獲得當前工做目錄
            if os.path.isfile(filepath):  #判斷文件是否存在
                exist_file_size = struct.unpack('i', self.conn.recv(4))[0] self.filepath = filepath header_dic = { 'filename': filename, 'file_md5': self.getfile_md5(), 'file_size': os.path.getsize(self.filepath) } header_bytes = pickle.dumps(header_dic) if exist_file_size:  #表示以前被下載過一部分
                    self.conn.send(struct.pack('i', len(header_bytes))) self.conn.send(header_bytes) if exist_file_size != os.path.getsize(self.filepath): with open(self.filepath, 'rb') as f: f.seek(exist_file_size) for line in f: self.conn.send(line) else: print('斷電和文件自己大小同樣') else:    #文件第一次下載
                    self.conn.send(struct.pack('i', len(header_bytes))) self.conn.send(header_bytes) with open(self.filepath, 'rb') as f: for line in f: self.conn.send(line) else: print('當前目錄下文件不存在') self.conn.send(struct.pack('i',0)) else: print('用戶沒用輸入文件名') def recursion_file(self,menu): '''遞歸查詢用戶home/alice目錄下的全部文件,算出文件的大小''' res = os.listdir(menu) #指定目錄下全部的文件和目錄名
        for i in res: path = '%s %s' % (menu, i) if os.path.isdir(path): #判斷指定對象是否爲目錄
 self.recursion_file(path) elif os.path.isfile(path): self.home_bytes_size += os.path.getsize(path) def current_home_size(self): '''獲得當前用戶home/alice目錄的大小,字節/M''' self.home_bytes_size = 0 self.recursion_file(self.homedir_path) print('字節:', self.home_bytes_size)  # 單位是字節
        home_m_size = round(self.home_bytes_size / 1024 /1024, 1) print('單位M:', home_m_size)  #單位是: M

    def put(self): '''從client上傳文件到server當前工做目錄下'''
        if len(self.cmds) > 1: state_size = struct.unpack('i',self.conn.recv(4))[0] if state_size: self.current_home_size() #算出了home下已被佔用的大小self.home_bytes_size
                header_bytes = self.conn.recv(struct.unpack('i', self.conn.recv(4))[0]) header_dic = pickle.loads(header_bytes) print(header_dic) filename = header_dic.get('filename') file_size = header_dic.get('file_size') file_md5 = header_dic.get('file_md5') upload_filepath = os.path.join(os.getcwd(), filename) self.filepath = upload_filepath  #爲了全局變量讀取文件算md5時方便
                if os.path.exists(upload_filepath): #文件已經存在
                    self.conn.send(struct.pack('i', 1)) has_size = os.path.getsize(upload_filepath) if has_size == file_size: print('文件已經存在') self.conn.send(struct.pack('i', 0)) else: #上次沒有傳完,接着繼續傳
                        self.conn.send(struct.pack('i', 1)) if self.home_bytes_size + int(file_size - has_size) > self.quota_bytes: print('超出了用戶的配額') self.conn.send(struct.pack('i', 0)) else: self.conn.send(struct.pack('i',1)) self.conn.send(struct.pack('i', has_size)) with open(upload_filepath, 'ab') as f: f.seek(has_size) while has_size < file_size: recv_bytes = self.conn.recv(self.MAX_RECV_SIZE) f.write(recv_bytes) has_size += len(recv_bytes) self.conn.send(struct.pack('i', has_size))  #爲了顯示進度條
                            if self.getfile_md5() == file_md5:  #判斷下載下來的文件MD5值和server傳過來的MD5值是否一致
                                print('\033[1;32m上傳成功\033[0m') self.conn.send(struct.pack('i', 1)) else: print('\033[1;32m上傳失敗\033[0m') self.conn.send(struct.pack('i', 0)) else: #第一次上傳
                    self.conn.send(struct.pack('i', 0)) if self.home_bytes_size + int(file_size) > self.quota_bytes: print('\033[1;32m超出了用戶的配額\033[0m') self.conn.send(struct.pack('i', 0)) else: self.conn.send(struct.pack('i', 1)) with open(upload_filepath, 'wb') as f: recv_size = 0 while recv_size < file_size: file_bytes = self.conn.recv(self.MAX_RECV_SIZE) f.write(file_bytes) recv_size += len(file_bytes) self.conn.send(struct.pack('i', recv_size)) #爲了進度條的顯示

                        if self.getfile_md5() == file_md5:  #判斷下載下來的文件MD5值和server傳過來的MD5值是否一致
                            print('\033[1;32m上傳成功\033[0m') self.conn.send(struct.pack('i', 1)) else: print('\033[1;32m上傳失敗\033[0m') self.conn.send(struct.pack('i', 0)) else: print('待傳的文件不存在') else: print('用戶沒有輸入文件名') def ls(self): '''查詢當前工做目錄下,先返回文件列表的大小,再返回查詢的結果''' subpro_obj = subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = subpro_obj.stdout.read() stderr = subpro_obj.stderr.read() self.conn.send(struct.pack('i', len(stdout + stderr))) self.conn.send(stdout) self.conn.send(stderr) def mkdir(self): '''在當前目錄下,增長目錄'''
        if len(self.cmds) > 1: mkdir_path = os.path.join(os.getcwd(),self.cmds[1]) if not os.path.exists(mkdir_path): #查看目錄名是否存在
 os.mkdir(mkdir_path) print('增長目錄成功') self.conn.send(struct.pack('i', 1)) #增長目錄成功,返回1
            else: print('目錄名已存在') self.conn.send(struct.pack('i', 0))  #失敗返回0
        else: print('用戶沒有輸入目錄名') def cd(self): '''切換目錄'''
        if len(self.cmds) > 1: dir_path = os.path.join(os.getcwd(), self.cmds[1]) if os.path.isdir(dir_path) :#查看是不是目錄名
                previous_path = os.getcwd() #拿到當前工做的目錄
                os.chdir(dir_path) #改變工做目錄到 . . .
                target_dir = os.getcwd() if self.homedir_path in target_dir: #判斷homedir_path是否在目標目錄
                    print('切換成功') self.conn.send(struct.pack('i', 1)) #切換成功返回1
                else: print('切換失敗')   #切換失敗後,返回到以前的目錄下
 os.chdir(previous_path) self.conn.send(struct.pack('i', 0)) else: print('要切換的目錄不在該目錄下') self.conn.send(struct.pack('i', 0)) else: print('沒有傳入切換的目錄名') def remove(self): '''刪除指定的文件,或者空文件夾'''
        if len(self.cmds) > 1: file_name = self.cmds[1] file_path = '%s\%s' %(os.getcwd(), file_name) if os.path.isfile(file_path): os.remove(file_path) self.conn.send(struct.pack('i', 1)) elif os.path.isdir(file_path):  #刪除空目錄
                if not len(os.listdir(file_path)): os.removedirs(file_path) print('刪除成功') self.conn.send(struct.pack('i', 1)) else: print('文件夾非空,不能刪除') self.conn.send(struct.pack('i', 0)) else: print('不是文件也不是文件夾') self.conn.send(struct.pack('i', 0)) else: print('沒有輸入要刪除的文件') def server_handle(self): '''處理與用戶的交互指令'''
        if self.auth(): print('\033[1;32m用戶登錄成功\033[0m') while True: try:    #try ...except 適合windows client斷開
                    user_input = self.conn.recv(self.MAX_RECV_SIZE).decode('utf-8') # if not user_input: continue #這裏適合 linux client斷開
                    self.cmds = user_input.split() if hasattr(self,self.cmds[0]): getattr(self,self.cmds[0])() else: print('\033[1;31請用戶重複輸入\033[0m') except Exception: break

    def run(self): self.server_accept() def close(self): self.socket.close() #if __name__ == '__main__':
    #pool = ThreadPoolExecutor(10)

user_handle.py併發

#_*_ coding:utf-8 _*_ # Author:Simon # Datetime:2019/8/14 10:26 # Software:PyCharm

import configparser import hashlib import os from conf import settings class UserHandle(): def __init__(self,username): self.username= username self.config = configparser.ConfigParser() #先生成一個對象
 self.config.read(settings.ACCOUNTS_FILE) @property def password(self): '''生成用戶的默認密碼 zhurui'''
        return hashlib.md5('zhurui'.encode('utf-8')).hexdigest() @property def quota(self): '''生成每一個用戶的磁盤配額''' quota = input('請輸入用戶的磁盤配額大小>>>:').strip() if quota.isdigit(): return quota else: exit('\033[1;31m磁盤配額必須是整數\033[0m') def add_user(self): '''建立用戶,存到accounts.ini'''
        if not self.config.has_section(self.username): print('creating username is : ', self.username) self.config.add_section(self.username) self.config.set(self.username, 'password', self.password) self.config.set(self.username, 'homedir', 'home/'+self.username) self.config.set(self.username, 'quota', self.quota) with open(settings.ACCOUNTS_FILE, 'w') as f: self.config.write(f) os.mkdir(os.path.join(settings.BASE_DIR, 'home', self.username)) #建立用戶的home文件夾
            print('\033[1;32m建立用戶成功\033[0m') else: print('\033[1;32m用戶已存在\033[0m') def judge_user(self): '''判斷用戶是否存在'''
        if self.config.has_section(self.username): return self.config.items(self.username) else: return

client客戶端

download文件是儲存下載的文件;upload是上傳文件的儲存庫(download裏邊能夠不放東西,等待下載便可;upload裏邊放你準備上傳給服務端的文件)app

ftp_client.pysocket

#_*_ coding:utf-8 _*_ # Author:Simon # Datetime:2019/8/14 11:12 # Software:PyCharm

import os import sys import socket import struct import pickle import hashlib class Ftpclient(): HOST = '127.0.0.1'  #服務器IP
    PORT = 8080 #服務端的端口
    MAX_RECV_SIZE = 8192 DOWNLOAD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download') UPLOAD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload') def __init__(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connect() def connect(self): '''鏈接服務端server'''
        try: self.socket.connect((self.HOST, self.PORT)) except Exception: exit('\033[1;31mserver還未啓動\033[0m') def get_recv(self): '''獲取server返回的數據'''
        return pickle.loads(self.socket.recv(self.MAX_RECV_SIZE)) def auth(self): '''用戶認證''' count = 0 while count < 3: name = input('請輸入用戶名>>:').strip() if not name: continue password = input('請輸入密碼>>:').strip() user_dic = { 'username':name, 'password':password } self.socket.send(pickle.dumps(user_dic)) #把用戶名和密碼發送給server
            res = struct.unpack('i',self.socket.recv(4))[0] if res:   #接收返回的信息,並判斷
                print('welcome'.center(20,'-')) user_info_dic = self.get_recv() self.username = user_info_dic.get('username') print(user_info_dic) return True else: print('\033[1;31m用戶名或者密碼不對!\033[0m') count += 1

    def readfile(self): '''讀取文件,獲得的文件內容的bytes型''' with open(self.filepath, 'rb') as f: filedata = f.read() return filedata def getfile_md5(self): '''對文件內容md5'''
        return hashlib.md5(self.readfile()).hexdigest() def progress_bar(self, num, get_size, file_size): '''進度條顯示''' float_rate = get_size / file_size # rate = str(float_rate * 100)[:5] # 95.85%
        rate = round(float_rate * 100,2)   # 95.85%

        if num == 1: #1表示下載
            sys.stdout.write('\r已下載:\033[1;32m{0}%\033[0m'.format(rate)) elif num == 2:   #2 表示上傳
            sys.stdout.write('\r已上傳:\033[1;32m{0}%\033[0m'.format(rate)) sys.stdout.flush() def get(self): '''從server下載文件到client'''
        if len(self.cmds) > 1: filename = self.cmds[1] self.filepath = os.path.join(self.DOWNLOAD_PATH, filename) #結合目錄名和文件名
            if os.path.isfile(self.filepath): #若是文件存在,支持斷點續傳
                temp_file_size = os.path.getsize(self.filepath) self.socket.send(struct.pack('i', temp_file_size)) header_size = struct.unpack('i', self.socket.recv(4))[0] if header_size:  #若是存在
                    header_dic = pickle.loads(self.socket.recv(header_size)) print(header_dic) filename = header_dic.get('filename') file_size = header_dic.get('file_size') file_md5 = header_dic.get('file_md5') if temp_file_size == file_size: print('\033[1;32m文件已經存在\033[0m') else: print('\033[1;33m正在進行斷點續傳....\033[0m') download_filepath = os.path.join(self.DOWNLOAD_PATH, filename) with open(download_filepath, 'ab') as f: f.seek(temp_file_size) get_size = temp_file_size while get_size < file_size: file_bytes = self.socket.recv(self.MAX_RECV_SIZE) f.write(file_bytes) get_size += len(file_bytes) self.progress_bar(1, get_size, file_size) # 1表示下載
                        if self.getfile_md5() == file_md5:  #判斷下載下來的文件MD5值和server傳過來的MD5值是否一致
                            print('\n\033[1;32m下載成功\033[0m') else: print('\n\033[1;32m下載文件大小與源文件大小不一致,請從新下載,將會支持斷點續傳033[0m') else: print('\n\033[1;31m該文件,以前被下載了一部分,可是server端的該文件,已被刪除,沒法再次下載\033[0m') else:  #文件第一次下載
                self.socket.send(struct.pack('i', 0))  # 0 表示以前沒有下載過
                header_size = struct.unpack('i', self.socket.recv(4))[0] if header_size: header_dic = pickle.loads(self.socket.recv(header_size)) print(header_dic) filename = header_dic.get('filename') file_size = header_dic.get('file_size') file_md5 = header_dic.get('file_md5') download_filepath = os.path.join(self.DOWNLOAD_PATH, filename) with open(download_filepath, 'wb') as f: get_size = 0 while get_size < file_size: file_bytes = self.socket.recv(self.MAX_RECV_SIZE) f.write(file_bytes) get_size += len(file_bytes) self.progress_bar(1, get_size, file_size) #1表示下載
                            print('總大小:%s已下載:%s'% (file_size, get_size)) if self.getfile_md5() == file_md5:  #判斷下載下來的文件MD5值和server傳過來的MD5值是否一致
                        print('\n\033[1;32m恭喜你,下載成功\033[0m') else: print('\n\033[1;32m下載失敗,再次下載支持斷點續傳\033[0m') else: print('\n\033[1;32m當前目錄下,文件不存在\033[0m') else: print('用戶沒有輸入文件名') def put(self): '''往server本身的home/alice目錄下,當前工做的目錄下上傳文件'''
        if len(self.cmds) > 1:   #確保用戶輸入了文件名
            filename = self.cmds[1] filepath = os.path.join(self.UPLOAD_PATH, filename) if os.path.isfile(filepath): self.socket.send(struct.pack('i', 1)) self.filepath = filepath filesize = os.path.getsize(self.filepath) header_dic = { 'filename': filename, 'file_md5': self.getfile_md5(), 'file_size': filesize } header_bytes = pickle.dumps(header_dic) self.socket.send(struct.pack('i',len(header_bytes))) self.socket.send(header_bytes) state = struct.unpack('i', self.socket.recv(4))[0] if state: #已經存在了
                    has_state = struct.unpack('i', self.socket.recv(4))[0] if has_state: quota_state = struct.unpack('i',self.socket.recv(4))[0] if quota_state: has_size = struct.unpack('i', self.socket.recv(4))[0] with open(self.filepath, 'rb') as f: f.seek(has_size) for line in f: self.socket.send(line) recv_size = struct.unpack('i', self.socket.recv(4))[0] self.progress_bar(2, recv_size, filesize) success_state = struct.unpack('i', self.socket.recv(4))[0] '''這裏必定要判斷,由於最後一次send(line)以後等待server返回, server返回,最後一次的recv_size==file_size,但client已經跳出了循環, 因此在for外面接收的success_state其實時file_size,這種狀況只針對大文件 '''
                            if success_state == filesize: success_state = struct.unpack('i', self.socket.recv(4))[0] if success_state: print('\n\033[1;32m恭喜您,上傳成功\033[0m') else: print('\n\033[1;32m上傳失敗\033[0m') else:  #超出了配額
                            print('\033[1;31m超出了用戶的配額\033[0m') else:  #存在的大小和文件大小一致,沒必要再傳
                        print('\033[1;31m當前目錄下,文件已經存在\033[0m') else:  #第一次傳
                    quota_state = struct.unpack('i', self.socket.recv(4))[0] if quota_state: with open(self.filepath, 'rb') as f: send_bytes = b''
                            for line in f: self.socket.send(line) send_bytes += line print('總大小:%s 已上傳:%s' % (filesize, len(send_bytes))) recv_size = struct.unpack('i',self.socket.recv(4))[0] self.progress_bar(2, recv_size, filesize) succes_state = struct.unpack('i',self.socket.recv(4))[0] if succes_state == filesize: succes_state = struct.unpack('i',self.socket.recv(4))[0] if succes_state: print('\n\033[1;32m恭喜您,上傳成功\033[0m') else: print('\n\033[1;32m上傳失敗\033[0m') else:   #超出了配額
                        print('\033[1;31m超出了用戶的配額\033[0m') else:  #文件不存在
                print('\033[1;31m文件不存在\033[0m') self.socket.send(struct.pack('i', 0)) else: print('用戶沒有輸入文件名') def ls(self): '''查詢當前工做目錄下,文件列表''' dir_size = struct.unpack('i', self.socket.recv(4))[0] recv_size = 0 recv_bytes = b''
        while recv_size < dir_size: temp_bytes = self.socket.recv(self.MAX_RECV_SIZE) recv_bytes += temp_bytes recv_size += len(temp_bytes) print(recv_bytes.decode('gbk'))  #gbk適合windows,utf-8適合linux


    def mkdir(self): '''增長目錄'''
        if len(self.cmds) > 1: res = struct.unpack('i', self.socket.recv(4))[0] if res: print('\033[1;32m在當前目錄下,增長目錄: %s 成功\033[0m'% self.cmds[1]) else: print('\033[1;31m增長目錄失敗\033[0m') else: print('沒有輸入要增長的目錄名') def cd(self): '''切換目錄'''
        if len(self.cmds) > 1: res = struct.unpack('i', self.socket.recv(4))[0] if res: print('\033[1;32m切換成功\033[0m') else: print('\033[1;32m切換失敗\033[0m') else: print('沒有輸入要切換的目錄名') def remove(self): '''刪除指定的文件,或者文件夾'''
        if len(self.cmds) > 1: res = struct.unpack('i', self.socket.recv(4))[0] if res: print('\033[1;32m刪除成功\033[0m') else: print('\033[1;31m刪除失敗\033[0m') else: print('沒有輸入要刪除的文件') def interactive(self): '''與server交互'''
        if self.auth(): while True: try: user_input = input('[%s]>>>:'%self.username) if not user_input:continue self.socket.send(user_input.encode('utf-8')) self.cmds = user_input.split() if hasattr(self, self_cmds[0]): getattr(self, self.cmds[0])() else: print('請從新輸入') except Exception as e: #server關閉了
                    print(e) break


    def close(self): self.socket.close() if __name__ == '__main__': ftp_client = Ftpclient() ftp_client.interactive() ftp_client.close()
相關文章
相關標籤/搜索