import socket
import hashlib
importstructimport os
import settingclass FtpClient(object):
features= ['get', 'put', 'ls', 'cd', 'mkdir', 'rm'] # 客户端提供的命令提供功能
def __init__(self, server_ip, port, user, pwd):''':param server_ip: 服务器ip
:param port: 服务器端口
:param user: 用户名
:param pwd: 密码''' self.server_ip =server_ip
self.port=port
self.user=user
self.pwd=pwd
self.sock=socket.socket() # 初始化时创建套接字try:
self.sock.connect((self.server_ip, self.port)) # 连接服务器
except ConnectionRefusedError:
print('501', setting.CODE['501'])returnself.pwd_hash() # 密码hashifnot self.auth():return# 验证失败
# self.run() # 登录成功继续操作
def pwd_hash(self): # 对用户输入密码进行hash
md5= hashlib.md5(setting.MD5_SALT.encode('utf-8'))
md5.update(self.pwd.encode('utf-8'))
self.pwd=md5.hexdigest()
def auth(self): # 上传用户密码进行验证
account= ','.join([self.user, self.pwd])
self.sock.send(account.encode('utf-8'))
auth_result= self.sock.recv(1024).decode() # 获取登录结果if auth_result == '8000':
print(auth_result, setting.CODE[auth_result])
self.run()else:
print(auth_result, setting.CODE[auth_result])
self.sock.close()returnFalse
def run(self):whileTrue:
user_input= input('\n>>>').strip() # 用户输入命令
command=user_input.split()
self.cmd= command[0] # 获取命令前缀if self.cmd == 'exit': # 检测到exit则退出
self.sock.close()break
else:if self.cmd inself.features: # 判断命令是否支持
self.command_s= user_input.encode('utf-8')
func=getattr(self, self.cmd) # 使用反射获取命令对应方法if len(command) == 1:if command[0] in ['get', 'put', 'cd', 'mkdir', 'rm']:
print(setting.CODE['4000'])continuefunc()
elif len(command)== 2:
func(command[1])else:
print(setting.CODE['4000'])else:
print(setting.CODE['4000'])
defget(self, *args, **kwargs):
self.send_cmd(self.command_s)
filename= args[0]
file_stat= self.sock.recv(4).decode('utf-8') # 接收文件是否找到状态码if file_stat == '1000':
tmp_file= filename + '.tmp'file_path=os.path.join(setting.DOWNLOAD_DIR, tmp_file) # 存放文件路径
real_path=os.path.join(setting.DOWNLOAD_DIR, filename)
tmp_size= 0
ifnot os.path.exists(file_path):
self.sock.sendall(b'2000')else:
self.sock.sendall(b'2001')
tmp_size=os.path.getsize(file_path)
self.sock.sendall(self.struck_info(tmp_size))
file_size= struct.unpack('i', self.sock.recv(4))[0] # 接收文件大小
receive_size= 0receive_size+=tmp_size
with open(file_path,'ab') asf:while receive_size
data= self.sock.recv(file_size -receive_size)else:
data= self.sock.recv(1024)ifnot data:
print('文件下载中断!')returnreceive_size+=len(data)
f.write(data)
self.toolbar(receive_size, file_size)
print()
src_md5= self.sock.recv(1024).decode('utf-8')
dec_md5=self.file_md5(file_path)if src_md5 ==dec_md5:
print('{}下载成功!'.format(filename))
os.rename(file_path, real_path)else:
print('文件一致性校验失败,上传失败')
os.remove(file_path)else:
print(setting.CODE[file_stat])
def put(self,*args, **kwargs):'''上传文件,断点续传
:param args:
:param kwargs:
:return:''' ifargs: # args为文件名
file_name= args[0]ifos.path.exists(file_name): # 判断文件是否存在
self.send_cmd(self.command_s)
file_size=os.path.getsize(file_name)
self.sock.send(self.struck_info(file_size)) # 发送文件大小
ques= self.sock.recv(1024).decode('utf-8') # 获取是否需要断点续传
send_size= 0# 发送的数据计数
with open(file_name,'rb') asf: # 打开文件if ques == 'all_file': # 发送整个文件
start_index= 0
else:
start_index= int(ques) # 获取断点文件大小
f.seek(start_index)
send_size+=start_indexfor line inf:
self.sock.sendall(line) # 发送数据
send_size+=len(line)
self.toolbar(send_size, file_size) # 进度条
print()
src_md5=self.file_md5(file_name) # 文件md5
self.sock.send(src_md5.encode('utf-8')) # 发送自己文件的md5
result= self.sock.recv(1024).decode('utf-8') # 获取md5比对结果,
print(result)else:
print('{} file not found!'.format(file_name))
def ls(self,*args, **kwargs):
self.send_cmd(self.cmd.encode('utf-8'))'''接收打印服务器发送的当前目录信息
:param args:
:param kwargs:
:return:''' info = self.sock.recv(4)
info_size= struct.unpack('i', info)[0] # 接收数据长度
receive_size= 0info= b''
while receive_size
data_size= 1024
if info_size - receive_size < 1024:
data_size= info_size -receive_size
data=self.sock.recv(data_size)
info+=data
receive_size+=len(data)
print(info.decode('utf-8')) # 打印目录结构
def cd(self,*args, **kwargs):'''切换目录
:param args:
:param kwargs:
:return:'''self.send_cmd(self.command_s)
print(setting.CODE[self.sock.recv(4).decode()]) # 打印切换结果
def mkdir(self,*args, **kwargs):'''创建目录
:param args:
:param kwargs:
:return:'''self.send_cmd(self.command_s)
status= self.sock.recv(1024).decode('utf-8')
print(setting.CODE[status])
def rm(self,*args, **kwargs):'''删除文件或者目录
:param args:
:param kwargs:
:return:'''self.send_cmd(self.command_s)
status= self.sock.recv(1024).decode('utf-8')
print(setting.CODE[status])
def struck_info(self, info_size):'''打包数据
:param info_size:
:return:''' return struct.pack('i', info_size)
def send_cmd(self, cmd):'''发送命令长度包和命令信息包
:param cmd:
:return:'''self.sock.send(self.struck_info(len(cmd)))
self.sock.send(cmd)
def file_md5(self, file):'''获取文件md5
:param file:
:return:''' md5 =hashlib.md5()
with open(file,'rb') asf:for i inf:
md5.update(i)returnmd5.hexdigest()
def toolbar(self, current, total):'''打印进度条
:param current:
:param total:
:return:''' val = current / total * 100print('\r{}{:.2f}%'.format(int(val) //2 * '>', val), end='')
if __name__ == '__main__':
user= input('请输入用户名:').strip()
pwd= input('请输入密码').strip()
cli= FtpClient('127.0.0.1', 8001, user, pwd)