1 程序要求
2 程序目录及文件结构
说明:
A 程序运行前,需事先建立好用户json文件及测试用用户家目录
B 先运行服务器端程序,后运行客户端程序
C 由于时间不充裕,最后一条要求未能完成,还有就是尽量输入正确的文件名或路径,否则程序会跳出(除输入多个“\”会产生问题外,还没有测试其它特殊符号)
3 服务器代码
#!/usr/bin/env python
# --author lisheng--
import socketserver
import json
import os
import sys
import hashlib
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.sys.path.append(base_path)
class ftpserver(socketserver.BaseRequestHandler):
def handle(self):
"""
用户加密认证及所有命令的解耦方法,每次连接时会刷新与客户端交互用的use_info字典
:return: none
"""
self.user_info = {} #服务器端的临时字典
print("主机连入:", self.client_address)
while True:
try:
x = self.ser_auth()
if x:
self.user_info = x # md5验证
self.user_info["path"] = r"%s\%s" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))),x["name"])
self.user_info["now_path"] = r"%s\%s" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))),x["name"])
while True:
try:
file_dict = json.loads(self.request.recv(1024).decode()) # 收交互用的临时字典
xcom = getattr(self, file_dict["active"].split()[0])
xcom(file_dict)
except Exception as e:
print("err", e)
break
except Exception as e:
print("err", e)
break
def jisuan(self,dir):
"""
计算目录空间大小的方法
:param dir: 要计算大小的目录名
:return: 返回计算结果
"""
mulu_size = 0
for root, dirs, list in os.walk(dir):
mulu_size += sum([os.stat(os.path.join(root, name)).st_size for name in list])
print(dir, "size:", mulu_size)
return mulu_size
def put(self, file_dict):
"""
客户端向服务器端上传文件的方法
:param file_dict: 解耦方法处获得的用户字典
:return:
"""
if os.path.isfile("%s\%s" %(self.user_info["now_path"],file_dict["filename"])):
file_dict["status_code"] = "201" #文件已存在
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送字典
else:
now_size = self.jisuan(self.user_info["path"]) + file_dict["size"]
print("user space:",self.user_info["space"],"now size:",now_size)
if now_size < self.user_info["space"]:
file_dict["status_code"] = "200" #可以开始上传
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送字典
f = open("%s\%s" %(self.user_info["now_path"],file_dict["filename"]), mode="ab")
recv_file_len = 0
# print("字典里文件大小的数据类型",type(file_dict["size"]))
while recv_file_len < file_dict["size"]:
if file_dict["size"] - recv_file_len > 1024:
recv_file = self.request.recv(1024)
f.write(recv_file) # 收文件
sys.stdout.write("*")
f.flush()
recv_file_len += len(recv_file)
else:
recv_file = self.request.recv(file_dict["size"] - recv_file_len)
f.write(recv_file)
sys.stdout.write("*")
f.flush()
recv_file_len += len(recv_file) # 收文件
print("\n收到文件大小,传来的字典中的文件大小:", recv_file_len, file_dict["size"])
f.flush()
f.close()
print("接收文件完成")
else:
file_dict["status_code"] = "202" #空间不足
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送字典
def get(self, file_dict):
"""
客户端下载方法
:param file_dict: 解耦方法处获得的用户字典
:return:
"""
file_name = "%s\%s" %(self.user_info["now_path"],file_dict["filename"])
print(file_name)
if os.path.isfile(file_name):
file_dict["status_code"] = "300"
file_dict["size"] = os.stat(file_name).st_size
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送字典
ack = self.request.recv(1024)
print("ack:", ack)
f = open(file_name, "rb")
print("开始发送文件")
for i in f:
self.request.send(i) # 发送文件
sys.stdout.write("*")
f.close()
print("\n文件传输完成")
else:
file_dict["status_code"] = "301"
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送字典
def dir(self, file_dict):
"""
客户端查看当前家目录内文件列表的方法
:param file_dict: 解耦方法处获得的用户字典
:return:
"""
print("命令:", file_dict["active"])
print(self.user_info["now_path"])
com_result = os.popen("%s %s" %(file_dict["active"],self.user_info["now_path"])).read()
if com_result:
file_dict["status_code"] = "400"
file_dict["res_len"] = len(com_result.encode("utf-8"))
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送状态码及命令字典
self.request.recv(1024)
self.request.send(com_result.encode("utf-8"))
print("from dir:", com_result)
else:
file_dict["status_code"] = "401"
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送状态码及命令字典
def chdir(self, file_dict):
"""
查看当前客户端的家目录信息
:param file_dict: 解耦方法处获得的用户字典
:return:
"""
print("命令:", file_dict["active"])
now_path = "\%s\%s" %(self.user_info["name"],os.path.relpath(self.user_info["now_path"],self.user_info["path"]))
self.request.send(now_path.encode("utf-8"))
print("now_path:",now_path)
def cd(self, file_dict):
"""
修改家目录路径,仅能在家目录内修改路径
:param file_dict: 解耦方法处获得的用户字典
:return:
"""
path = file_dict["active"].split( )[1]
print("命令:",file_dict["active"])
if path == "..":
if self.user_info["path"] in os.path.dirname(self.user_info["now_path"]):
self.user_info["now_path"] = os.path.dirname(self.user_info["now_path"])
file_dict["status_code"] = "500" # 成功修改路径
print("path",self.user_info["path"],"now_path",self.user_info["now_path"])
elif len(self.user_info["path"]) > len(os.path.dirname(self.user_info["now_path"])):
file_dict["status_code"] = "502" # 您没有权限
else:
if os.path.exists("%s%s" %(self.user_info["now_path"],path)):
self.user_info["now_path"] += path
file_dict["status_code"] = "500" # 成功修改路径
print("path",self.user_info["path"],"now_path",self.user_info["now_path"])
else:
file_dict["status_code"] = "501" # 路径不存在
self.request.send(json.dumps(file_dict).encode("utf-8")) # 发送状态码及命令字典
def ser_auth(self):
"""
客户端登陆前的加密认证
:return: 返回用户信息字典
"""
cli_auth = json.loads(self.request.recv(1024).decode())
print(cli_auth)
if os.path.isfile("%s\conf\%s.json" %(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),cli_auth[0])):
# self.request.send(b"101") #有用户,判断是否正确
f = open("%s\conf\%s.json" % (os.path.dirname(os.path.dirname(os.path.abspath(__file__))), cli_auth[0]),"r")
user = json.loads(f.read())
print(user)
f.close()
u_p = hashlib.md5()
u_p.update(b"%s%s" % (user["name"].encode("utf-8"), user["password"].encode("utf-8")))
md5_res = u_p.hexdigest()
print(md5_res)
if cli_auth[1] == md5_res:
self.request.send(b"100") #验证通过
return user
else:
self.request.send(b"102") #密码错误
else:
self.request.send(b"101") #无此用户
if __name__ == "__main__":
host, port = "0.0.0.0", 9999
ser = socketserver.ThreadingTCPServer((host, port), ftpserver)
ser.serve_forever()
4 客户端代码
#!/usr/bin/env python
#Author:lisheng
import socket
import os
import json
import sys
import hashlib
class client(object):
def __init__(self):
self.con = socket.socket()
self.file_dict = {}
def connet(self,ip,port):
"""
连接ftp服务器
:param ip: ftp服务器地址
:param port: ftp服务器端口
:return: none
"""
self.con.connect((ip,port))
def help(self):
"""
帮助信息
:return: none
"""
print("""
dir
chdir
get
cd
put
""")
def auth_cli(self):
"""
登陆前的加密认证
:return: 认证通过后返回用户名
"""
i = 1
while i < 4:
au = hashlib.md5()
name = input("name:")
if name == "q":break
password = input("password:")
au.update(name.encode("utf-8"))
au.update(password.encode("utf-8"))
au_md5 = au.hexdigest()
cli = []
cli.append(name)
cli.append(au_md5)
self.con.send(json.dumps(cli).encode("utf-8")) # 发送用户名,用作目录名
code = self.con.recv(1024).decode() # 防粘包
if code == "100":
print("验证通过")
i = 4
return name
elif code == "101":
print("无此用户")
elif code == "102":
print("密码错误")
print(i)
i += 1
def action(self):
"""
所有命令的解耦方法,每次连接时会刷新与服务器交互用的file_dict字典
:return: none
"""
while True:
x = self.auth_cli()
if x:
self.file_dict["name"] = x
while True:
self.file_dict = {
"filename": None,
"size": None,
"override": None,
"active": None,
"status_code": None,
"res_len": None,
"name": None
}
commond = r"%s" % input("%s # " % (self.file_dict["name"])).strip()
if len(commond) == 0:continue
elif commond == "q":break
elif hasattr(self,commond.split( )[0]):
self.file_dict["active"] = commond
xcom = getattr(self,commond.split( )[0])
xcom()
else:
print("请检查命令是否正确")
self.help()
else:
print("bye")
break
def put(self):
"""
上传文件方法。上传超过用户空间配额时,会阻止上传
:return: none
"""
if os.path.isfile(self.file_dict["active"].split()[1]):
self.file_dict["filename"] = self.file_dict["active"].split()[1]
self.file_dict["size"] = os.stat(self.file_dict["active"].split()[1]).st_size
self.file_dict["active"] = "put"
self.con.send(json.dumps(self.file_dict).encode("utf-8")) # 发送文件字典
ser_dict = json.loads(self.con.recv(1024).decode()) #接收字典
if ser_dict["status_code"] == "200":
f = open(self.file_dict["filename"],"rb")
print("开始发送文件")
for i in f:
self.con.send(i) #发送文件
sys.stdout.write("*")
f.close()
print("\n文件传输完成")
elif ser_dict["status_code"] == "202":
print("服务器上空间不足")
elif ser_dict["status_code"] == "201":
print("服务器上文件重名")
else:
print("请检查文件名是否正确")
def get(self):
"""
下载文件
:return: none
"""
if os.path.isfile(self.file_dict["active"].split()[1]):
print("该文件已存在")
else:
self.file_dict["filename"] = self.file_dict["active"].split()[1]
self.file_dict["active"] = "get"
self.con.send(json.dumps(self.file_dict).encode("utf-8")) # 发送文件字典
ser_dict = json.loads(self.con.recv(1024).decode()) # 接收字典
if ser_dict["status_code"] == "300":
self.con.send(b"ack")
recv_file_len = 0
f = open(ser_dict["filename"],"ab")
# print("字典里文件大小的数据类型",type(file_dict["size"]))
while recv_file_len < ser_dict["size"]:
if ser_dict["size"] - recv_file_len > 1024:
recv_file = self.con.recv(1024)
f.write(recv_file) # 收文件
sys.stdout.write("*")
f.flush()
recv_file_len += len(recv_file)
else:
recv_file = self.con.recv(ser_dict["size"] - recv_file_len)
f.write(recv_file) # 收文件
sys.stdout.write("*")
f.flush()
recv_file_len += len(recv_file) # 收文件
print("\n收到文件大小,传来的字典中的文件大小:", recv_file_len, ser_dict["size"])
f.flush()
f.close()
print("接收文件完成")
else:
print(ser_dict["status_code"])
print("服务器上无此文件")
def dir(self):
"""
查看用户当前在服务器上的家目录,只能看到家目录的相对路径,无法看到服务器上的其它路径
:return: none
"""
self.con.send(json.dumps(self.file_dict).encode("utf-8")) # 发送文件字典
ser_dict = json.loads(self.con.recv(1024).decode()) #接收状态码及命令字典
self.con.send(b"ack")
if ser_dict["status_code"] == "400":
rec_com = "".encode("utf-8")
while len(rec_com) < ser_dict["res_len"]:
if ser_dict["res_len"] - len(rec_com) > 1024:
rec_com += self.con.recv(1024)
else:
rec_com += self.con.recv(ser_dict["res_len"] - len(rec_com))
print(rec_com.decode(),"服务器返回的命令大小(编码后):",ser_dict["res_len"],"收到的命令大小(编码后):",len(rec_com),"收到的命令大小(解码后):",len(rec_com.decode()))
else:
print("命令错误或没有结果")
def chdir(self):
"""
修改用户路径,只能在家目录里切换目录
:return:none
"""
self.con.send(json.dumps(self.file_dict).encode("utf-8")) # 发送文件字典
serv_res = self.con.recv(1024).decode() #接收当前目录信息
print(serv_res)
def cd(self):
"""
修改用户在服务器上的相对家目录
:return:none
"""
if " " in self.file_dict["active"].strip():
self.con.send(json.dumps(self.file_dict).encode("utf-8")) # 发送文件字典
ser_dict = json.loads(self.con.recv(1024).decode()) # 接收状态码及命令字典
print("更改路径后的状态码:",ser_dict["status_code"])
if ser_dict["status_code"] == "501":
print("路径不存在")
elif ser_dict["status_code"] == "502":
print("您没有权限")
elif ser_dict["status_code"] == "500":
print("路径修改成功")
else:
print("有问题")
if __name__ == "__main__":
a = client()
a.connet("localhost",9999)
a.action()
5 用户文件格式为.json格式,内容如下
{“name”: “ls”, “password”: “1234”, “space”: 102400000}