要求:
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.py

#_*_ 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(这个可以在执行中创建)

[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.py

#_*_ 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.py

# _*_ 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
1、启动ftp服务器
2、创建用户
3、退出\033[0m\n
'''
msg_dic = {'': 'start_ftp', '': 'create_user', '': '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):
'''处理用户的认证请求
1、根据username读取accounts.ini文件,password相比,判断用户是否存在
2、将程序运行的目录从bin/ftp_server.py修改到用户home/alice,方便之后查询ls
3、给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里边放你准备上传给服务端的文件)

ftp_client.py

#_*_ 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()

Python之路【第十五篇】开发FTP多线程程序的更多相关文章

  1. Python之路(第二十五篇) 面向对象初级:反射、内置方法

    [TOC] 一.反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究.它 ...

  2. Python之路(第十五篇)sys模块、json模块、pickle模块、shelve模块

    一.sys模块 1.sys.argv 命令行参数List,第一个元素是程序本身路径 2.sys.exit(n) 退出程序,正常退出时exit(0) 3.sys.version . sys.maxint ...

  3. Python之路【第五篇】:面向对象及相关

    Python之路[第五篇]:面向对象及相关   面向对象基础 基础内容介绍详见一下两篇博文: 面向对象初级篇 面向对象进阶篇 其他相关 一.isinstance(obj, cls) 检查是否obj是否 ...

  4. Python之路【第五篇】:面向对象和相关

    Python之路[第五篇]:面向对象及相关   面向对象基础 基础内容介绍详见一下两篇博文: 面向对象初级篇 面向对象进阶篇 其他相关 一.isinstance(obj, cls) 检查是否obj是否 ...

  5. Python之路【第五篇】python基础 之初识函数(一)和文件管理

    转载请注明出处http://www.cnblogs.com/wupeiqi/articles/5453708.html 函数 一.背景                                 ...

  6. Python自动化 【第十五篇】:CSS、JavaScript 和 Dom介绍

    本节内容 CSS javascript dom CSS position标签 fixed: 固定在页面的某个位置 relative + absolute: 相对定位 opacity:0.5 设置透明度 ...

  7. Python之路【第五篇】:面向对象编程

    面向对象编程思维导向图

  8. 【Python之路】第五篇--Python基础之杂货铺

    字符串格式化 Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存. 1.百分号方式 ...

  9. Python之路(第二十九篇) 面向对象进阶:内置方法补充、异常处理

    一.__new__方法 __init__()是初始化方法,__new__()方法是构造方法,创建一个新的对象 实例化对象的时候,调用__init__()初始化之前,先调用了__new__()方法 __ ...

随机推荐

  1. 【搜索】$P1092$虫食算

    题目链接 首先,我们只考虑加法的虫食算.这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0. 其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同 ...

  2. 同余and乘法逆元学习笔记

    目录 数学符号 快速幂 方法一 方法二 同余 概念 同余的性质 乘法逆元 概念: 求逆元的方法 扩展欧几里得 快速幂法\(o(n*log(n))\) 递推法\(o(n)\) sjp大佬让我写同余那就只 ...

  3. 中心极限定理(Central Limit Theorem)

    中心极限定理:每次从总体中抽取容量为n的简单随机样本,这样抽取很多次后,如果样本容量很大,样本均值的抽样分布近似服从正态分布(期望为  ,标准差为 ). (注:总体数据需独立同分布) 那么样本容量n应 ...

  4. c++ map内置类型的默认值(std::map default value for build-in type)

    大神的帖子,留着自己备忘:http://www.it1352.com/455626.html 结论:你看到的value是整数.浮点(初始化为零)的行为是由标准定义的,你可以依赖它. 网上还有好多帖子说 ...

  5. lintcode- 22.平面表

    题目描述 22. 平面列表 给定一个列表,该列表中的每个要素要么是个列表,要么是整数.将其变成一个只包含整数的简单列表. 样例 给定 [1,2,[1,2]],返回 [1,2,1,2]. 给定 [4,[ ...

  6. IDEA 调试图文教程,让 bug 无处藏身!

    阅读本文大概需要 6.2 分钟. 来源:http://t.cn/EoPN7J2 Debug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行 ...

  7. 方法型混淆js代码

    const fs = require('fs'); const acorn = require('acorn'); const walk = require("acorn-walk" ...

  8. ios手机竖屏拍照图片旋转90°问题解决方法

    手机拍照会给图片添加一个Orientaion信息(即拍照方向),如下: 用ios手机拍照,系统会给图片加上一个方向的属性, ios相机默认的拍照方向是后摄Home键在右为正,前摄Home键在左为正. ...

  9. FZU Monthly-201906 获奖名单

    FZU Monthly-201906 获奖名单 冠军: 空缺 一等奖: 陈金杰 S031702334 空缺 二等奖: 黄海东 S031702647 吴宜航 S031702645 蔡煜晖 S111801 ...

  10. Segment fault 常见原因

    [https://blog.csdn.net/qq_22238021/article/details/79872978] 本质原因在于:程序访问了非法的地址 1.引用空指针 2.野指针 3.访问越界 ...