
#ftp_server.py
import socket,json,hashlib,struct,os,socketserver
#md5加密类
class getMd5(object):
# 普通加密用于账户密码加密方法
@classmethod
def makeMd5(cls,msg):
md5=hashlib.md5(b‘salt!@#$‘)
md5.update(msg.encode(‘utf-8‘))
return md5.hexdigest()
# 图片加密用于数据校验方法
@classmethod
def noEncodeMd5(cls,msg):
md5 = hashlib.md5(b‘salt!@#$‘)
md5.update(msg)
return md5.hexdigest()
#数据校验类
class DataVer(object):
# 接收数据校验方法,guanDao信息管道,RecvData接收的数据
@classmethod
def RecvDataVer(cls,guanDao,recvData):
print(‘开始MD5校验...‘)
md5_data_len = struct.unpack(‘i‘, guanDao.recv(4))[0] # 解包
md5_data = guanDao.recv(md5_data_len).decode(‘gbk‘) # 解码得到客户端加密数据字符串
if md5_data == getMd5.noEncodeMd5(recvData): # 对比加密字符串
guanDao.send(‘ok‘.encode(‘gbk‘))
print(‘数据校验成功,数据正确!!!!‘)
else:
print(‘数据校验失败,数据错误‘)
# (断点续传)发送数据相同性校验方法
@classmethod
def ddxcRecvDataVer(cls, guanDao, file_path):
client_md5DataInfoDicJsonStr_len = struct.unpack(‘i‘, guanDao.recv(4))[0]# 解包
client_md5DataInfoDic_JsonStr = guanDao.recv(client_md5DataInfoDicJsonStr_len).decode(‘gbk‘)# 解码得到客户端加密数据字符串
client_md5DataInfo_Dic = json.loads(client_md5DataInfoDic_JsonStr) # 得到客户端文件信息的字典
loaded_len=client_md5DataInfo_Dic[‘client_Data_len‘]#服务端要读取的长度
with open(file_path,‘rb‘)as f:
server_data=f.read(loaded_len)#读服务端数据
if getMd5.noEncodeMd5(server_data)==client_md5DataInfo_Dic[‘client_md5Data‘]:#对比服务端数据加密数据与客户端加密数据对比
guanDao.send(struct.pack(‘i‘,1))
print(‘数据校验成功,可以进行断点续传‘)
return loaded_len
else:
guanDao.send(struct.pack(‘i‘,0))
print(‘数据校验失败,不可以进行断点续传‘)
return 0
# 发送数据校验方法,guanDao信息管道,sendData发送的数据
@classmethod
def SendDataVer(cls,guanDao,sendData):
print(‘开始MD5数据校验...‘)
MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串
guanDao.send(struct.pack(‘i‘, len(MD5_data))) # 打包
guanDao.send(MD5_data.encode(‘gbk‘)) # 编码发送加密数据字符串
if guanDao.recv(1024).decode(‘gbk‘) == ‘ok‘: # 对比返回数据
print(‘数据校验成功,数据正确!!!!‘)
else:
print(‘数据校验失败,数据错误‘)
# (断点续传)接收数据相同性校验方法
@classmethod
def ddxcSendDataVer(cls, guanDao, sendData,data_fromServer_len):#sendData 数据 data_fromServer_len数据长度
MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串
#发送数据相关信息字典
sendData_info_dic={
‘MD5_data‘:MD5_data,
‘data_fromServer_len‘:data_fromServer_len
}
sendData_infoDic_jsonstr=json.dumps(sendData_info_dic)#字典变成json字符串
guanDao.send(struct.pack(‘i‘, len(sendData_infoDic_jsonstr))) # 打包发送json字符串长度
guanDao.send(sendData_infoDic_jsonstr.encode(‘gbk‘)) # 编码发送json字符串长度
#程序流程类
class ProgramProcess(object):
# 主页面方法
@classmethod
def MainPage(cls,conn):
gongNeng_lst = [‘login‘, ‘zhuce‘] # 功能列表
while 1:
print(‘接受执行哪个方法的指令。。。‘)
fangshi=conn.recv(1024).decode(‘gbk‘)
# print(‘字符串指令:‘,fangshi)
conn.send(‘ok‘.encode(‘gbk‘))
# print(‘发送了确认接收的指令ok‘)
if fangshi.upper()==‘Q‘:
print(‘程序结束‘)
break
func=getattr(cls,gongNeng_lst[int(fangshi)-1])#反射
func(conn)#运行对应方法
# 注册方法
@classmethod
def zhuce(cls,conn):
while 1:
print(‘等待接受数据。。。‘)
from_client_name = conn.recv(1024).decode(‘utf-8‘)
print(‘接收到的数据为:‘,from_client_name)
if from_client_name.upper()==‘Q‘:
print(‘返回主界面‘)
break
with open(‘user_info‘, ‘a+‘, encoding=‘utf-8‘)as f:
f.seek(0)#把光标移到开头
for line in f:
if from_client_name == line.strip().split(‘|‘)[0]:
print(‘账号已存在,不允许注册‘)
conn.send(json.dumps(0).encode(‘utf-8‘))
break
else:
print(‘允许注册‘)
conn.send(json.dumps(1).encode(‘utf-8‘))
from_client_info=conn.recv(1024).decode(‘utf-8‘)
name=from_client_info.split(‘|‘)[0]#获取到名字
neiCun=input(‘请给新用户分配内存空间M(空为默认值50M):‘)
if neiCun==‘‘:
neiCun=‘50‘
f.write(from_client_info+‘|‘+neiCun+‘\n‘)#把账号|密码写入文件
file_path=r‘.\server_file‘ + ‘//‘ + name#生成账户路径
os.makedirs(file_path)#新建账户文件夹
print(‘注册成功!!!‘)
conn.send(json.dumps(1).encode(‘utf-8‘))
# 登录方法
@classmethod
def login(cls,conn):
flag = 0
while not flag:
print(‘等待接受数据。。。‘)
from_client_usrInfo = conn.recv(1024).decode(‘gbk‘)#获取账号
if from_client_usrInfo.upper()==‘Q‘:
print(‘返回主界面‘)
break
with open(‘user_info‘, ‘r‘, encoding=‘utf-8‘)as f:
for line in f:
if from_client_usrInfo in line.strip():#数据比对成功
print(‘数据比对成功‘)
conn.send(json.dumps(1).encode(‘utf-8‘))
if conn.recv(1024).decode(‘gbk‘)==‘ok‘:#收到为‘ok’
conn.send(line.strip().encode(‘gbk‘))#把文件name|psw|neiCun数据发送过去
name,psw,neiCun=line.strip().split(‘|‘)#把此行数据分割放入对象
obj = Account(name, psw, neiCun)#实例化对象
obj.sendPage(conn)#进入第二界面
flag = 1
break
else:
print(‘数据比对失败‘)
conn.send(json.dumps(0).encode(‘utf-8‘))
#账户及操作相关类
class Account(object):
# 初始化方法
def __init__(self,name,psw,neiCun):
self.name=name
self.psw=psw
self.neiCun=neiCun
self.path=r‘.\server_file‘+‘//‘+name
# 功能主页面方法
def sendPage(self,conn):
gongNeng_lst = [‘update‘, ‘download‘,‘newdir‘,‘deldir‘ ] # 功能列表
while 1:
print(‘接受执行哪个方法的指令。。。‘)
num_str=conn.recv(1024).decode(‘gbk‘)
# print(‘字符串指令:‘,fangshi)
conn.send(‘ok‘.encode(‘gbk‘))
# print(‘发送了确认接收的指令ok‘)
if num_str.upper()==‘Q‘:
print(‘返回主界面‘)
break
if num_str==‘5‘:#当字符串数字为5时
print(‘选择的功能:查看文件夹‘)
Account.show(conn,self.path)#运行遍历文件夹方法
else:
func=getattr(self,gongNeng_lst[int(num_str)-1])#反射
func(conn)#运行对应方法
# 上传文件方法
def update(self,conn):
print(‘选择的功能:上传文件‘)
while 1:
updata_path=self.path+‘//‘#默认上传文件到自己的文件夹
client_fileinfo_len=struct.unpack(‘i‘,conn.recv(4))[0]#解包4字节json字典字符串长度
if client_fileinfo_len:#发过来的数据不是0
# print(‘file_info字典json字符串的长度:‘,client_fileinfo_len)
client_fileinfo_dic=json.loads(conn.recv(client_fileinfo_len).decode(‘gbk‘))#接收file_info字典
if client_fileinfo_dic[‘updataPath‘] == ‘‘:#判断字典里的上传保存路径是否为空
updata_path=self.path#拼接上传文件的路径上层文件夹的路径
updata_path_new=self.path+‘//‘+client_fileinfo_dic[‘filename‘]#此路径是上传数据写文件的路径
else:
updata_path=os.path.join(updata_path,client_fileinfo_dic[‘updataPath‘])#拼接上传文件的路径上层文件夹的路径
updata_path_new=updata_path+‘//‘+client_fileinfo_dic[‘filename‘]#此路径是上传数据写文件的路径
if os.path.exists(updata_path):#判断上传文件保存路径是否存在
conn.send(struct.pack(‘i‘,1))#发送打包数据1
fromclient_wenjian_length = client_fileinfo_dic[‘filesize‘] # 上传文件的大小
print(‘上传文件的字节长度:‘, fromclient_wenjian_length)
usedNeiCun = Account.CalculateFileSize(self.path) # 计算已使用内存
usedNeiCun = usedNeiCun / (1024 * 1024) # 转换成M
SurplusNeiCun = float(self.neiCun) - float(usedNeiCun) # 剩余内存
print(‘剩余内存:‘, SurplusNeiCun)
fromclient_wenjian_len = fromclient_wenjian_length / (1024 * 1024) # 上传文件大小M
if SurplusNeiCun > fromclient_wenjian_len: # 内存可接受这么大的文件
conn.send(struct.pack(‘i‘, 1)) # 内存可接受这么大数据向服务端发送打包1
if os.path.exists(updata_path_new):#判断文件路径updata_path_new是否存在相同的文件
conn.send(struct.pack(‘i‘,1)) #此路径文件已存在发送打包1数据
print(‘上传的文件已存在,请进行MD5数据校验看数据是否完整‘)
self.duanDianXuChuan(conn,updata_path_new,fromclient_wenjian_length)#进入断点续传方法,把管道,与路径传过去
else:#不存在此文件路径时
conn.send(struct.pack(‘i‘,0)) #此路径文件不存在发送打包0数据
recv_data_len = 0 # 接收数据的长度
recv_data = b‘‘
with open(updata_path_new, ‘wb‘)as f: # 打开服务端接收文件的
while recv_data_len < fromclient_wenjian_length: # 当接收数据长度小于总数据长度时进入循环
data = conn.recv(1024)
f.write(data)
recv_data_len += len(data)
recv_data += data
per_cent = recv_data_len * 100 // fromclient_wenjian_length
# 通过\r来实现同一行打印,每次打印都回到行首打印
print(‘\r‘ + ‘%d%% %s‘ % (per_cent, ‘>‘ * (per_cent // 5)), end=‘‘)
else:
conn.send(struct.pack(‘i‘, 1)) # 向客户端发送接收完毕的数据
print(‘\n‘ + ‘上传接收完毕,接收字节长度为:‘, recv_data_len)
DataVer.RecvDataVer(conn, recv_data) # 调用接收数据校验方法
usedNeiCun = Account.CalculateFileSize(self.path) # 计算已使用内存
usedNeiCun = usedNeiCun / (1024 * 1024) # 转换成M
conn.send(struct.pack(‘f‘, usedNeiCun)) # 返回已使用内存
else:
conn.send(struct.pack(‘i‘, 0)) # 内存不足发送0
conn.send(struct.pack(‘f‘, SurplusNeiCun))
print(‘内存不足‘)
else:
conn.send(struct.pack(‘i‘,0))
print(‘上传文件保存路径不存在‘)
else:
print(‘退出上传功能‘)
break
# 下载文件方法
def download(self,conn):
print(‘选择的功能:下载文件‘)
while 1:
file_path_new=conn.recv(1024).decode(‘gbk‘)#接受要下载的文件名称
if file_path_new.upper()==‘Q‘:
print(‘退出下载功能‘)
break
file_path=self.path+‘//‘+file_path_new#默认服务器给客户端发送的文件路径
if os.path.exists(file_path):#判断文件路径是否存在
conn.send(struct.pack(‘i‘,1))#路径存在发送四字节1
file_info={
‘filename‘:os.path.basename(file_path_new),
‘filepath‘:file_path,
‘filesize‘:os.path.getsize(file_path)
}#文件信息组成的字典
print(‘文件字节长度:‘, file_info[‘filesize‘])
conn.send(struct.pack(‘i‘,len(json.dumps(file_info))))#发送file_info字典序列化成字符串的长度
conn.send(json.dumps(file_info).encode(‘gbk‘))#发送file_info字典序列化后的编码
if struct.unpack(‘i‘,conn.recv(4))[0]:#判断客户端是否存在要下载的文件
print(‘客户端已存在此文件,下面进行数据校验‘)
self.downLoadDuanDianXuChuan(conn,file_path)#调用断点续传方法
else:
send_data_len = 0#发送数据长度
send_data=b‘‘#发送的数据
with open(file_path, ‘rb‘)as f:
while send_data_len<file_info[‘filesize‘]:
data=f.read(1024)
conn.send(data)
send_data_len+=len(data)
send_data+=data
per_cent = send_data_len*100 // file_info[‘filesize‘]
# 通过\r来实现同一行打印,每次打印都回到行首打印
print(‘\r‘ + ‘%d%% %s‘ % (per_cent, ‘>‘ * (per_cent // 5)), end=‘‘)
else:
if conn.recv(1024).decode(‘utf-8‘)==‘ok‘:
print(‘\n‘+‘下载数据发送完毕,发送字节长度为:‘,send_data_len)
DataVer.SendDataVer(conn,send_data)#调用发送数据校验方法
else:
conn.send(struct.pack(‘i‘,0))#文件路径不存在发送4字节数值0
print(‘文件路径不存在‘)
# 新建文件夹方法
def newdir(self,conn):
print(‘选择的功能:新建文件夹‘)
while 1:
path=conn.recv(1024).decode(‘gbk‘)
if path.upper()==‘Q‘:
print(‘退出新建文件功能‘)
break
new_path=self.path+‘//‘+path#拼接新建文件夹的路径
new_name = os.path.basename(path) # 获取新建文件夹的名字
new_path_dir=new_path.replace(‘//‘+new_name,‘‘)#获取上层文件夹路径
if os.path.exists(new_path_dir):#判断上层文件件路径是否存在
conn.send(struct.pack(‘i‘,1))
os.makedirs(new_path)#新建文件夹
else:
conn.send(struct.pack(‘i‘,0))
print(‘路径不存在‘)
# 删除文件夹方法
def deldir(self,conn):
print(‘选择的功能:删除文件夹‘)
while 1:
path = conn.recv(1024).decode(‘gbk‘)
if path.upper()==‘Q‘:
print(‘退出删除文件功能‘)
break
new_path = self.path + ‘//‘ + path # 拼接删除文件夹的路径
new_name = os.path.basename(path) # 获取删除文件夹的名字
new_path_dir = new_path.replace(‘//‘ + new_name, ‘‘) # 获取上层文件夹路径
if os.path.exists(new_path_dir): # 判断上层文件件路径是否存在
conn.send(struct.pack(‘i‘, 1))
os.removedirs(new_path) # 删除文件夹
else:
conn.send(struct.pack(‘i‘, 0))
print(‘路径不存在‘)
#上传的断点续传的方法
def duanDianXuChuan(self,conn,updata_path_new,fromclient_wenjian_length):
#updata_path_new 文件上传到服务端的保存路径
#fromclient_wenjian_length 上传文件的总长度
with open(updata_path_new,‘rb‘)as f:#打开服务端文件
data_from_server=f.read()#服务端数据
data_fromServer_len=len(data_from_server)#服务端数据的长度
DataVer.ddxcSendDataVer(conn,data_from_server,data_fromServer_len)#调用数据校验方法
if struct.unpack(‘i‘, conn.recv(4))[0]:
print(‘可以进行断点续传‘)
with open(updata_path_new,‘ab+‘)as f:
while data_fromServer_len<fromclient_wenjian_length:
data=conn.recv(1024)
f.write(data)#继续上次数据上传
data_fromServer_len+=len(data)
percent=data_fromServer_len*100//fromclient_wenjian_length
print(‘\r %d%% %s ‘%(percent,‘>‘*(percent//5)),end=‘‘)
else:
print(‘\n断点续传完成‘)
f.seek(0)
datas=f.read()
from_clientdata_md5str=conn.recv(len(getMd5.noEncodeMd5(datas))).decode(‘gbk‘)
if from_clientdata_md5str==getMd5.noEncodeMd5(datas):
conn.send(struct.pack(‘i‘,1))
print(‘md5数据校验成功!!!‘)
else:
conn.send(struct.pack(‘i‘,0))
print(‘MD5数据校验失败!!!‘)
else:
print(‘数据不一致不能进行断点续传‘)
#下载的断点续传方法
def downLoadDuanDianXuChuan(self,conn,file_path):
loaded_len=DataVer.ddxcRecvDataVer(conn,file_path)#调用数据校验方法
if loaded_len:#进入断点续传的条件
server_data_len=os.path.getsize(file_path)#服务端数据字节长度
with open(file_path,‘rb‘)as f:
f.seek(loaded_len)#光标挪到读到的位置
while loaded_len < server_data_len:
data=f.read(1024)
conn.send(data)#发送断点数据
loaded_len+=len(data)
percent=(loaded_len*100)//server_data_len
print(‘\r %d%% %s‘%(percent,‘>‘*(percent//5)),end=‘‘)
else:
print(‘\n完成了断点续传‘)
f.seek(0)
datas=f.read()
conn.send(getMd5.noEncodeMd5(datas).encode(‘gbk‘))
if struct.unpack(‘i‘,conn.recv(4))[0]:
print(‘MD5数据校验成功!!‘)
else:
print(‘MD5数据校验失败!!‘)
# 查看文件夹方法
@staticmethod
def show(conn,file_path):
nameList = os.listdir(file_path) # 获取账号根目录下的name列表
conn.send(json.dumps(nameList).encode(‘gbk‘)) # json转字符串编码发送
while 1:
name=conn.recv(1024).decode(‘gbk‘)#接收客户端发来的name
if name.upper()==‘Q‘:
print(‘返回上层文件夹‘)
break
new_path=file_path+‘//‘+name#拼接新路径
if os.path.isdir(new_path):#判断路径是否为文件夹
conn.send(struct.pack(‘i‘,1))#是文件夹发送打包的1
Account.show(conn,new_path)#进入递归
else:
conn.send(struct.pack(‘i‘,0))#如果不是文件夹发送给客户端打包0
# 计算文件夹大小的方法
@staticmethod
def CalculateFileSize(path):
usedNeiCun=0
list_name=os.listdir(path)#获取路径下的文件和文件夹名字的列表
for name in list_name:
path_abs=os.path.join(path,name)#拼接路径
if os.path.isfile(path_abs):#如果是文件
usedNeiCun+=os.path.getsize(path_abs)#计算文件大小
elif os.path.isdir(path_abs):#如果是文件夹
size=Account.CalculateFileSize(path_abs)#接受内层计算的值
usedNeiCun+=size
return usedNeiCun #返回已经使用的内存大小
#多线程通信类
class myServer(socketserver.BaseRequestHandler):
#获取信息管道方法
def handle(self):
print(‘连接成功!!!‘)
conn=self.request
ProgramProcess.MainPage(conn)
print(‘断开连接,等待下次连接>>>>>>>>>>>>>‘)
conn.close()
#程序入口
if __name__ == ‘__main__‘:
socketserver.TCPServer.allow_reuse_address = True#允许端口重用
server = socketserver.ThreadingTCPServer((‘127.0.0.1‘, 8001), myServer)
server.serve_forever()
#ftp_client.py
import socket,json,hashlib,struct,os
#md5加密类
class getMd5(object):
# 普通加密用于账户密码加密方法
@classmethod
def makeMd5(cls,msg):
md5=hashlib.md5(b‘salt!@#$‘)
md5.update(msg.encode(‘utf-8‘))
return md5.hexdigest()
# 图片加密用于数据校验方法
@classmethod
def noEncodeMd5(cls,msg):
md5 = hashlib.md5(b‘salt!@#$‘)
md5.update(msg)
return md5.hexdigest()
#数据校验类
class DataVer(object):
# 接收数据校验方法,guanDao信息管道,RecvData接收的数据
@classmethod
def RecvDataVer(cls,guanDao,recvData):
print(‘开始MD5校验...‘)
md5_data_len = struct.unpack(‘i‘, guanDao.recv(4))[0] # 解包
md5_data = guanDao.recv(md5_data_len).decode(‘gbk‘) # 解码得到客户端加密数据字符串
if md5_data == getMd5.noEncodeMd5(recvData): # 对比加密字符串
guanDao.send(‘ok‘.encode(‘gbk‘))
print(‘数据校验成功,数据正确!!!!‘)
else:
print(‘数据校验失败,数据错误‘)
# (断点续传)发送数据相同性校验方法
@classmethod
def ddxcRecvDataVer(cls, guanDao, file_path):#_file_path 本地上传文件路径
sendData_jsonstr_len = struct.unpack(‘i‘, guanDao.recv(4))[0] # 解包
sendData_infoDic_jsonstr = guanDao.recv(sendData_jsonstr_len).decode(‘gbk‘) # 解码得到客户端json字符串
sendData_info_dic=json.loads(sendData_infoDic_jsonstr)#发送数据相关信息字典
read_data_len=sendData_info_dic[‘data_fromServer_len‘]#读文件的长度
with open(file_path,‘rb‘)as f:
client_read_data=f.read(read_data_len)
#read_data_len读文件的长度
#client_read_data 读取的数据
if getMd5.noEncodeMd5(client_read_data)==sendData_info_dic[‘MD5_data‘]:#数据相同可以断点续传
guanDao.send(struct.pack(‘i‘,1))#发送打包数据1
print(‘数据相同可以断点续传‘)
return read_data_len
else:
guanDao.send(struct.pack(‘i‘, 0)) # 发送打包数据0
print(‘同一路径下数据不同,无法进行断点续传‘)
return 0
# 发送数据校验方法,guanDao信息管道,sendData发送的数据
@classmethod
def SendDataVer(cls, guanDao, sendData):
print(‘开始MD5数据校验...‘)
MD5_data = getMd5.noEncodeMd5(sendData) # 加密数据字符串
guanDao.send(struct.pack(‘i‘, len(MD5_data))) # 打包
guanDao.send(MD5_data.encode(‘gbk‘)) # 编码发送加密数据字符串
if guanDao.recv(1024).decode(‘gbk‘) == ‘ok‘: # 对比返回数据
print(‘数据校验成功,数据正确!!!!‘)
else:
print(‘数据校验失败,数据错误‘)
# (断点续传)接收数据相同性校验方法
@classmethod
def ddxcSendDataVer(cls, guanDao,abs_download_path):
with open(abs_download_path,‘rb‘)as f:
client_Data=f.read()#读出服务端数据
client_md5Data=getMd5.noEncodeMd5(client_Data)#md5加密得到加密数据字符串
client_md5DataInfo_Dic={
‘client_md5Data‘:client_md5Data,
‘client_Data_len‘:len(client_Data)
}
client_md5DataInfoDic_JsonStr=json.dumps(client_md5DataInfo_Dic)#json字符串
guanDao.send(struct.pack(‘i‘, len(client_md5DataInfoDic_JsonStr))) # 打包
guanDao.send(client_md5DataInfoDic_JsonStr.encode(‘gbk‘)) # 编码发送加密数据字符串
if guanDao.recv(4)[0]:
print(‘数据校验成功,可以进行断点续传‘)
return len(client_Data)#已经下载的数据长度
else:
print(‘数据校验失败,不可以进行断点续传‘)
return 0
#程序流程类
class ProgramProcess(object):
# 程序入口的方法
@classmethod
def MainPage(cls):
client = sendMSG.buid_connct() # 调用生成client对象的方法
lst = [‘login‘, ‘zhuce‘]
while 1:
print(‘‘‘
欢迎进入FTP文件系统
****************************
1.登录
2.注册
****************************
‘‘‘)
num = input(‘请输入要执行的序号(按Q退出):‘)
if num==‘1‘or num==‘2‘or num.upper()==‘Q‘:#判断输入合法
client.send(num.encode(‘gbk‘))
if num.upper() == ‘Q‘:
print(‘程序退出>>>>>>>‘)
break
if client.recv(1024).decode(‘gbk‘) == ‘ok‘:
func = getattr(cls, lst[int(num) - 1])
func(client)
else:
print(‘输入有误‘)
client.close()
# 注册方法
@classmethod
def zhuce(cls, client):
while 1:
name = input(‘请输入注册的账号(按q退出):‘)
client.send(name.encode(‘utf-8‘))
if name.upper() == ‘Q‘:
break
if json.loads(client.recv(1024).decode(‘utf-8‘)):
psw = getMd5.makeMd5(input(‘请输入密码:‘))
print(‘等待服务端为您分配磁盘内存空间...‘)
if sendMSG.senD(client, name, psw):
print(‘注册成功!!!‘)
else:
print(‘账号已存在‘)
# 登陆方法
@classmethod
def login(cls, client):
while 1:
name = input(‘请输入账号(Q退出):‘)
if name.upper() == ‘Q‘:
client.send(name.encode(‘gbk‘))
break
psw = getMd5.makeMd5(input(‘请输入密码:‘))
if sendMSG.senD(client, name, psw):
client.send(‘ok‘.encode(‘gbk‘))
print(‘登陆成功!!!!‘)
account_info=client.recv(1024).decode(‘gbk‘)#接受name|psw|neiCun数据
name,psw,neiCun=account_info.split(‘|‘)#切割解构
obj=Acount(name,psw,neiCun)#实例化对象
obj.sendPage(client)#调用实例方法
break
else:
print(‘账号或密码错误‘)
#账户相关类
class Acount(object):
# 初始化方法
def __init__(self,name,psw,neiCun):
self.name=name
self.psw=psw
self.neiCun=neiCun
# 功能界面方法
def sendPage(self,client):
while 1:
print(‘‘‘
FTP系统主界面
****************************
1.上传文件
2.下载文件
3.新建文件夹
4.删除文件夹
5.查看文件夹
****************************
‘‘‘)
run_lst=[‘update‘,‘download‘,‘newdir‘,‘deldir‘,]
chioce_num=input(‘请输入要执行的数字(按Q退出):‘)
if chioce_num==‘1‘or chioce_num==‘2‘or chioce_num==‘3‘or chioce_num==‘4‘or chioce_num==‘5‘or chioce_num.upper()==‘Q‘:#判断输入合法性
client.send(chioce_num.encode(‘gbk‘))#发送方法编号 if client.recv(1024).decode(‘gbk‘)==‘ok‘:#接收到服务端返回的??数据
if chioce_num.upper() == ‘Q‘:
print(‘返回上一层‘)
break
if chioce_num==‘5‘:#如果输入的是‘5‘
print(‘选择的功能:查看文件夹‘)
Acount.show(client)#运行show静态方法
else:
func=getattr(self,run_lst[int(chioce_num)-1])#反射
func(client)#运行对应方法
else:
print(‘没有收到服务端返回数据‘)
else:
print(‘输入有误‘)
# 上传文件方法
def update(self,client):
print(‘选择的功能:上传文件‘)
while 1:
client_file_path=input(‘请输入上传文件的本地路径(Q退出):‘)
if client_file_path.upper()==‘Q‘:
client.send(struct.pack(‘i‘,0))#发送打包数据0
print(‘退出文件上传功能‘)
break
if os.path.exists(client_file_path):#本地上传路径存在
flag=0
while not flag:
updata_path=input(‘请输入保存到服务端的路径(层级用//隔开,为空是默认路径):‘)
file_info={
‘filename‘:os.path.basename(client_file_path),#文件信息字典
‘filepath‘:client_file_path,
‘filesize‘:os.path.getsize(client_file_path),
‘updataPath‘:updata_path
}#file_info字典
print(‘上传文件的字节长度:‘,file_info[‘filesize‘])
file_info_jsonstr=json.dumps(file_info)#字典变成json字符串
fileInfo_jsonstrlen_stru=struct.pack(‘i‘,len(file_info_jsonstr))#打包字典json字符串长度
client.send(fileInfo_jsonstrlen_stru)#发送字典长度包
client.send(file_info_jsonstr.encode(‘gbk‘)) # 发送字典file_info
if struct.unpack(‘i‘, client.recv(4))[0]: # 上传文件保存到服务端的路径存在
flag = 1
if struct.unpack(‘i‘,client.recv(4))[0]:#如果内存空间可以放下此文件大小
if struct.unpack(‘i‘,client.recv(4))[0]:#如果上传文件的路径下有与上传文件同名的文件
print(‘服务端路径下已有与上传文件同名文件,进行MD5比对中...‘)
self.duanDianXuChuan(client,client_file_path)#进入断点续传方法
else:#如果上传文件的路径下没有与上传文件同名的文件
send_data_len=0#发送的数据长度
send_data=b‘‘
with open(file_info[‘filepath‘],‘rb‘)as f:
while send_data_len<file_info[‘filesize‘]:#当接收的数据长度小于总数据长度时
data=f.read(1024)
client.send(data)
send_data_len+=len(data)
send_data+=data
per_cent = send_data_len*100//file_info[‘filesize‘]
# 通过\r来实现同一行打印,每次打印都回到行首打印
print(‘\r‘ + ‘%d%% %s‘ % (per_cent,‘>‘*(per_cent//5)),end=‘‘)
else:
if struct.unpack(‘i‘,client.recv(4))[0]:
print(‘\n‘+‘上传完毕,上传数据字节长度为‘,file_info[‘filesize‘])
DataVer.SendDataVer(client,send_data)#调用发送数据校验方法
usdNeiCun=struct.unpack(‘f‘,client.recv(4))[0]
print(‘总内存: %s M‘%self.neiCun)
print(‘已使用内存为: %d M‘% usdNeiCun)
else:
SurplusNeiCun=struct.unpack(‘f‘,client.recv(4))[0]#剩余空间
file_size=file_info[‘filesize‘]/(1024*1024)#上传文件大小M
print(‘内存剩余空间:%sM ‘%SurplusNeiCun)
print(‘文件大小: %sM‘%file_size)
print(‘内存空间不足!!!!!‘)
else:
print(‘文件保存到服务端的路径不存在!!!‘)
else:
print(‘本地上传文件路径不存在!!!!‘)
# 下载文件方法
def download(self,client):
print(‘选择的功能:下载文件‘)
while 1:
file_path=input(‘请输入要下载文件的路径(层级用//隔开,Q退出):‘).replace(‘ ‘,‘‘)
client.send(file_path.encode(‘gbk‘))#发送要下载的文件名
if file_path.upper()==‘Q‘:
print(‘退出下载功能‘)
break
if struct.unpack(‘i‘,client.recv(4))[0]:#如果文件名存在
download_path=r‘.\client_file‘+‘//‘ # 默认下载路径
print(‘等待接收服务端发送的文件...‘)
server_fileinfojsonstr_len = struct.unpack(‘i‘,client.recv(4))[0]#拆包4字节file_info字典的json字符串长度
# print(‘file_info字典的json字符串长度:‘, server_fileinfojsonstr_len)
server_fileinfo_jsonstr=client.recv(server_fileinfojsonstr_len).decode(‘gbk‘)#接收file_info字典的json字符串
file_info_dic=json.loads(server_fileinfo_jsonstr)#json反序列化变成file_info字典
fromclient_wenjian_length=file_info_dic[‘filesize‘]#获取文件长度
print(‘下载文件的字节长度:‘,fromclient_wenjian_length)
abs_download_path=os.path.join(download_path,file_info_dic[‘filename‘])#下载的全路径
if os.path.exists(abs_download_path):#下载到服务端的路径存在
client.send(struct.pack(‘i‘,1))#服务端存在要下载的文件
print(‘客户端已存在此文件,下面进行数据校验‘)
self.downLoadDuanDianXuChuan(client,abs_download_path,fromclient_wenjian_length)#调用断点续传方法
else:
client.send(struct.pack(‘i‘,0))#服务端不存在要下载的文件
recv_data_len=0#接收的文件字节长
recv_data=b‘‘
with open(abs_download_path, ‘wb‘)as f:
while recv_data_len < fromclient_wenjian_length:#当接受的字节长度总和小于数据总长度时进入循环
data = client.recv(1024)
f.write(data)#写入文件
recv_data_len += len(data)
recv_data+=data
# 进度条百分比前的数字X(X%)
per_cent = recv_data_len*100 // fromclient_wenjian_length
# 通过\r来实现同一行打印,每次打印都回到行首打印
print(‘\r‘ + ‘%d%% %s‘ % (per_cent, ‘>‘ * (per_cent // 5)), end=‘‘)
else:
client.send(‘ok‘.encode(‘utf-8‘))#发送接收完毕信号
print(‘\n‘+‘下载完毕,接收数据字节长度为:‘,recv_data_len)
DataVer.RecvDataVer(client,recv_data)#调用接收数据校验方法
else:
print(‘客户端不存在此文件‘)
# 遍历文件夹方法
@staticmethod
def show(client):
print(‘本层文件夹目录为:‘)
nameList = json.loads(client.recv(1024).decode(‘gbk‘))
while 1:
for name in nameList:
print(name)
new_name=input(‘请选择要继续查看的文件夹名(Q退出):‘)
if new_name.upper() ==‘Q‘:
client.send(new_name.encode(‘gbk‘)) # 把q发送给服务端
print(‘返回上层目录‘)
break
if new_name in nameList:#输入的名字在列表中
client.send(new_name.encode(‘gbk‘))#发送new_name
if struct.unpack(‘i‘,client.recv(4))[0]:#判断是不是文件夹
Acount.show(client)#进入递归
else:
print(‘此名字不是文件夹‘)
else:
print(‘本层目录不存在此文件夹名‘)
# 新建文件夹方法
def newdir(self,client):
print(‘选择的功能:新建文件夹‘)
while 1:
path=input(‘请输入新建夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):‘)
client.send(path.encode(‘gbk‘))
if path.upper()==‘Q‘:
print(‘退出新建文件功能‘)
break
if struct.unpack(‘i‘,client.recv(4))[0]:
print(‘新建文件夹成功!!‘)
else:
print(‘文件路径错误‘)
# 删除文件夹方法
def deldir(self,client):
print(‘选择的功能:删除文件夹‘)
while 1:
path=input(‘请输入删除夹路径(层级用//隔开),不熟悉请先选择查看文件夹方法(按q退出):‘)
client.send(path.encode(‘gbk‘))
if path.upper()==‘Q‘:
print(‘退出删除文件功能‘)
break
if struct.unpack(‘i‘,client.recv(4))[0]:
print(‘删除文件夹成功!!!‘)
else:
print(‘文件路径错误‘)
#上传断点续传方法
def duanDianXuChuan(self,cilent,client_file_path):
readed_data_len=DataVer.ddxcRecvDataVer(cilent,client_file_path)#文件从此位置开始读
if readed_data_len:#是否进行断点续传校验方法
print(‘服务端已经存在数据大小:‘,readed_data_len)
data_len=os.path.getsize(client_file_path)#文件总长度
with open(client_file_path,‘rb‘)as f:
f.seek(readed_data_len)#把光标放到断点位置
while readed_data_len <data_len:#已读长度小于总长度
data=f.read(1024)
readed_data_len+=len(data)
cilent.send(data)#发送断点数据
percent = readed_data_len * 100 // data_len
print(‘\r %d%% %s ‘ % (percent, ‘>‘ * (percent // 5)), end=‘‘)
else:
print(‘\n 断点续传完成‘)
f.seek(0)
datas=f.read()
cilent.send(getMd5.noEncodeMd5(datas).encode(‘gbk‘))
if struct.unpack(‘i‘,cilent.recv(4))[0]:
print(‘md5数据校验成功!!‘)
else:
print(‘MD5数据校验失败‘)
#下载断点续传方法
def downLoadDuanDianXuChuan(self,client,abs_download_path,fromclient_wenjian_length):
loaded_len=DataVer.ddxcSendDataVer(client,abs_download_path)#调用数据校验方法
if loaded_len:#进入断点续传的条件
with open(abs_download_path,‘ab+‘)as f:
while loaded_len < fromclient_wenjian_length:
data=client.recv(1024)
f.write(data)
loaded_len+=len(data)
percent=(loaded_len*100)//fromclient_wenjian_length
print(‘\r %d%% %s‘%(percent,‘>‘*(percent//5)),end=‘‘)
else:
print(‘\n断点续传完成‘)
f.seek(0)
datas=f.read()
fromserver_md5_str=client.recv(len(getMd5.noEncodeMd5(datas))).decode(‘gbk‘)
if fromserver_md5_str==getMd5.noEncodeMd5(datas):
client.send(struct.pack(‘i‘,1))
print(‘md5数据校验成功!!‘)
else:
client.send(struct.pack(‘i‘,0))
print(‘md5数据校验失败!!‘)
#发送信息相关类
class sendMSG(object):
# 建立连接方法,返回client对象
@classmethod
def buid_connct(cls):
client = socket.socket()
client.connect((‘127.0.0.1‘, 8001))
return client
# 发送数据user_info方法,返回账号密码是否正确(1/0)
@classmethod
def senD(cls,client,name,psw):
user_info = name + ‘|‘ + psw
# print(‘数据传输格式:‘,user_info)
client.send(user_info.encode(‘utf-8‘))
from_server_res=json.loads(client.recv(1024).decode(‘utf-8‘))
return from_server_res
#程序入口
if __name__==‘__main__‘:
ProgramProcess.MainPage()
原文:https://www.cnblogs.com/PythonMrChu/p/9837173.html