python做一个简单的ftp服务器核心代码
作业要求
1. 多用户同时登陆 v
2. 用户登陆,加密认证
3. 上传/下载文件,保证文件一致性
4. 传输过程中现实进度条
5. 不同用户家目录不同,且只能访问自己的家目录
6. 对用户进行磁盘配额、不同用户配额可不同
7. 用户登陆server后,可在家目录权限下切换子目录
8. 查看当前目录下文件,新建文件夹
9. 删除文件和空文件夹
10. 充分使用面向对象知识
11. 支持断点续传
客户端及服务端的目录设计
服务端
ftp_server
bin
db // 用户信息
home // 家目录
用户1
用户2
客户端
ftp_client
bin
conf # 文件下载的文件路径等相关配置
服务端执行代码
利用socketserver模块来实现多线程的服务器
import socketserver
import hashlib
import struct
import json
import os
BASE_PASH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HOME_PASH = os.path.join(BASE_PASH,"home")
class MyHandler(socketserver.BaseRequestHandler):
home_path = None
then_path = None
show_path = None
code_info={200:"ok",
400:"command not found",
401: "name or pwd error",
402: "parameter error",
}
def handle(self):
try:
while 1:
msg = self._parser()
cmd = msg.get("cmd")
if hasattr(self, cmd):
getattr(self, cmd)(msg)
else:
self._send_msg(code=400)
except ConnectionResetError:
self.request.close()
def _parser(self):
struct_info = self.request.recv(4)
msg_bytes_len = struct.unpack("i", struct_info)[0]
msg = str(self.request.recv(msg_bytes_len), encoding="utf8")
return json.loads(msg)
def _send_msg(self,**kwargs):
msg_bytes = bytes(json.dumps(kwargs), encoding="utf8")
self.request.send(struct.pack("i", len(msg_bytes)))
self.request.send(msg_bytes)
def login(self, msg):
name = msg.get("name")
pwd = msg.get("pwd")
if name=="alex" and pwd=="4399":
self.home_path = os.path.join(HOME_PASH, name)
self.then_path = self.home_path
self.show_path = self.then_path.replace(self.home_path,"\\")
self._send_msg(code=200, name=name, show_path = self.show_path)
else:
self._send_msg(code=401)
def ls(self, msg):
dir_lis = os.listdir(self.then_path)
if dir_lis:
dir_str = " ".join(dir_lis)
self._send_msg(code=200, dir_str=dir_str)
else:
self._send_msg(code=200, dir_str="")
def cd(self, msg):
path = msg.get("args")[0]
if path == "/":
pwd = self.home_path
elif path.startswith("/"):
print(self.home_path)
pwd = os.path.join(self.home_path, path[1:])
else:
pwd = os.path.join(self.then_path, path)
if os.path.isdir(pwd):
self.then_path = pwd
self.show_path = self.then_path.replace(self.home_path,"")
self._send_msg(code=200, show_path=self.show_path)
else:
self._send_msg(code=402)
def pull(self, msg):
file_name = msg.get("file_name")
file_path = os.path.join(self.then_path, file_name)
if os.path.isfile(file_path):
print("is file")
load_size = msg.get("load_size")
all_size = os.path.getsize(file_path)
md5 = self.get_md5(file_path)
self._send_msg(code=200, file_name=file_name, all_size=all_size,
md5=md5)
print(load_size, all_size)
with open(file_path, "rb") as f:
if load_size:
f.seek(load_size)
for i in f:
self.request.send(i)
else:
self._send_msg(code=402)
def get_md5(self, file_path):
abs = hashlib.md5()
with open(file_path, "rb") as f:
for i in f:
abs.update(i)
return abs.hexdigest()
if __name__ == "__main__":
sock = socketserver.ThreadingTCPServer(("127.0.0.1",8000),MyHandler)
sock.serve_forever()
客户端代码
import hashlib
import socket
import struct
import json
import os
LOAD_PATH = os.path.dirname(os.path.abspath(__file__))
class Client:
user = None
show_path = None
code_info = {200: "ok",
400: "command not found",
401: "name or pwd error",
402: "parameter error",
}
def __init__(self):
self.ip = "127.0.0.1"
self.port = 8000
def connect(self):
self.request = socket.socket()
self.request.connect((self.ip,self.port))
def login(self):
name, pwd = input("请输入账号"),input("请输入密码")
self.connect()
self.send_msg(cmd="login", name=name, pwd=pwd)
def send_msg(self,**kwargs):
msg_bytes = bytes(json.dumps(kwargs), encoding="utf8")
self.request.send(struct.pack("i", len(msg_bytes)))
self.request.send(msg_bytes)
def parser(self):
struct_info = self.request.recv(4)
msg_bytes_len = struct.unpack("i", struct_info)[0]
msg = str(self.request.recv(msg_bytes_len), encoding="utf8")
return json.loads(msg)
def forever(self):
while True:
self.login()
msg = self.parser()
if msg.get("code") == 200:
self.show_path = msg.get("show_path")
self.name = msg.get("name")
self.handle()
else:
print(self.code_info.get(msg.get("code")))
def handle(self):
while True:
cmd_line = input("[%s@%s]#"%(self.name, self.show_path)).strip()
if not cmd_line:continue
cmd_list = cmd_line.split(" ", 1)
cmd = cmd_list[0]
if hasattr(self, cmd):
getattr(self, cmd)(cmd_list)
def ls(self, cmd_list):
self.send_msg(cmd=cmd_list[0])
msg = self.parser()
code = msg.get("code")
if code == 200:
print(msg.get("dir_str"))
def cd(self, cmd_list):
self.send_msg(cmd=cmd_list[0], args=cmd_list[1:])
msg = self.parser()
code = msg.get("code")
if code == 200:
print(msg.get("show_path"))
self.show_path = msg.get("show_path")
else:
print(self.code_info.get(code))
def pull(self, cmd_list):
file_name = cmd_list[-1]
file_path = os.path.join(LOAD_PATH, file_name)
if os.path.isfile(file_path):
load_size = os.path.getsize(file_path)
else:
load_size = 0
self.send_msg(cmd=cmd_list[0], file_name=file_name,
load_size=load_size)
msg = self.parser()
code = msg.get("code")
if code == 200:
md5 = msg.get("md5")
all_size = msg.get("all_size")
print(load_size, all_size)
with open(file_path, "ab") as f:
while load_size < all_size:
ret = self.request.recv(8096)
f.write(ret)
load_size += len(ret)
self.progress(load_size, all_size)
local_md5 = self.get_md5(file_path)
print(md5, local_md5)
if md5 == local_md5:
print("文件一致性校验通过")
else:
print(self.code_info.get(code))
def push(self, cmd_list):
pass
def progress(self, load_size, all_size):
i = load_size*100 // all_size
char_num = i // 2 # 打印多少个'*'
per_str = '\r%s%% : %s\n' % (i, '*' * char_num) if i == 100 else '\r%s%% : %s' % (i, '*' * char_num)
print(per_str, end='', flush=True)
def get_md5(self, file_path):
abs = hashlib.md5()
with open(file_path, "rb") as f:
for i in f:
abs.update(i)
return abs.hexdigest()
if __name__ == "__main__":
Client().forever()
断点续传思路
下载文件时,判断本地下载目录中是否存在同名文件(已下载), 若存在读取其大小, 将其大小写入pack的字典中
服务端判断是否已下载, 若存在,则将光标移动至已下载位置(f.seek(光标位置)), 在读取发送
客户端则使用追加的方式写入

浙公网安备 33010602011771号