python多人登录上传下载其他功能后续加入

这我们来一起做一个简单的ftp系统,功能要求如下:

  • 1、多用户同时登陆
  • 2、用户登录,加密认证
  • 3、上传、下载文件,保证文件一致性
  • 4、传输过程中实现进度条
  • 5、不同用户目录不同,且只能访问用户自己目录
  • 6、对用户进行磁盘调配、不同用户调配可以不同
  • 7、用户登录server后可在其权限目录下子目录切换
  • 8、查看当前用户目录下文件,新建文件夹
  • 9、删除文件和空文件夹
  • 10、充分使用面向对象知识
  • 11、支持断点续传

其中5/6、7/8/9暂时缺失,用户登录没有做验证,后面做验证

系统结构如下:

image.png

bin-》index为入口文件

代码如下:

1
2
3
from core.handle import run
if __name__ == '__main__':
run()

config存放配置文件,基本上暂时用到很少,sys内容如下:

1
2
IP_PORT = ('10.10.10.103', 9998)
UPLOAD_PATH = '../uploads'

core中存放核心文件,代码量较少,都放到了handle中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import socketserver
import pickle
import os
from config import sys
import struct
import hashlib
'''
功能列表
'''
class Action:
def __init__(self, conn):
# 客户端连接句柄
self.conn = conn
self.username = None
self.pwd = None
def login(self, username, pwd):
self.username = username
self.pwd = pwd

# md5加密文件
def file_md5(self, file_path):
md5 = hashlib.md5(b'tony')
with open(file_path, 'rb') as f:
for line in f:
md5.update(line)
return md5.hexdigest()

def upload(self, file_info):
# 文件上传目录
file_dir = os.path.join(sys.UPLOAD_PATH, self.username)
# 文件名称
file_name = os.path.basename(file_info['file_path'])
# 已上传文件路径
file_md5_path = os.path.join(file_dir, file_info['file_md5'])
print(file_info, file_dir, '执行了服务器上传')
# 用户目录不存在则创建用户目录
if not os.path.exists(file_dir):
os.makedirs(file_dir)
# 获取已上传的文件大小,如果没有上传则已上传大小为0
exists_size = os.path.getsize(file_md5_path) if os.path.exists(file_md5_path) else 0
# 发送已上传文件大小
self.conn.send(struct.pack('i', exists_size))
with open(file_md5_path, 'ab') as f:
while exists_size < file_info['file_size']:
data = self.conn.recv(1024)
exists_size += len(data)
f.write(data)
os.rename(file_md5_path, os.path.join(file_dir, file_name))
# 下载文件
def download(self, cmd_info):

# 文件下载目录
file_dir = os.path.join(sys.UPLOAD_PATH, self.username)
# 获取下载文件路径
file = os.path.join(file_dir, cmd_info['file_path'])
print(cmd_info, file_dir, file, os.path.exists(file))
# 判断要下载的文件是否存在
if not os.path.exists(file):
self.conn.send(pickle.dumps({'code':1004, 'msg':'您要下载的文件不存在'}))
else:
file_size = os.path.getsize(file)
print(file_size)
# 发送给客户端下载文件信息,用来获取文件已下载大小
self.conn.send(pickle.dumps({'file_size':file_size, 'file_md5':self.file_md5(file)}))
# 接收客户端发来的文件已下载大小
exists_size = struct.unpack('i', self.conn.recv(4))[0]
# 发送文件
with open(file, 'rb') as f:
# 移动文件指针至已下载过的位置
f.seek(exists_size)
while exists_size < file_size:
# 每次读取1024大小
data = f.read(1024)
# 累加到已下载大小
exists_size += len(data)
# 发送当次读取数据
self.conn.send(data)

'''
主程序
'''
class Server(socketserver.BaseRequestHandler):
def handle(self):
print(self.client_address, '已经成功连接至服务器……')
action = Action(self.request)
if action.username:
self.request.send(pickle.dumps({'code': 1000, 'msg': '您已登录,请输入您要执行的命令:'}))
else:
self.request.send(pickle.dumps({'code': 1001, 'msg': '请输入用户名和密码:'}))
pickle_user = pickle.loads(self.request.recv(1024))
action.login(pickle_user['username'], pickle_user['pwd'])
while 1:
try:
cmd_info = pickle.loads(self.request.recv(1024))
if hasattr(action, cmd_info['cmd']):
getattr(action, cmd_info['cmd'])(cmd_info)
except Exception as e:
break
self.request.close()
print(self.client_address, '已经断开服务器……')
def run():
server = socketserver.ThreadingTCPServer(sys.IP_PORT, Server)
server.serve_forever()
print('系统开始运行……')

log日志目录暂时没写入,uploads为上传文件夹根目录,里面存放各用户上传的数据。

临时客户端代码直接写到了client文件中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import socket
import pickle
import os
import hashlib
import struct
from config import sys
client = socket.socket()
client.connect(sys.IP_PORT)
# md5加密文件
def file_md5(file_path):
md5 = hashlib.md5(b'tony')
with open(file_path, 'rb') as f:
for line in f:
md5.update(line)
return md5.hexdigest()

# 上传进度条
def upload_progress(current_size, total_size):
print('\r总大小:{}【{:50}】已上传:{:.2f}%'.format(total_size, '=' * (current_size * 50 // total_size), current_size/total_size*100), end='')
# 发送文件内容
def send_file(file_path, exists_size = 0):
# 获取文件大小
file_size = os.path.getsize(file_path)
# 读取文件
with open(file_path, 'rb') as f:
# 指针移动到已经上传大小的位置
f.seek(exists_size)
while exists_size < file_size:
# 每次读取1024
data = f.read(1024)
# 累计读取的大小
exists_size += len(data)
# 发送当次循环数据
client.send(data)
# 显示进度条
upload_progress(exists_size, file_size)

def upload(file_path):
# 封装文件信息
cmd_info = {'cmd' : 'upload', 'file_path':file_path, 'file_size':os.path.getsize(file_path), 'file_md5':file_md5(file_path)}
print(cmd_info)
# 序列化文件信息
cmd_info_picle = pickle.dumps(cmd_info)
# 发送文件信息
client.send(cmd_info_picle)
# 接收已上传大小
exists_size = struct.unpack('i', client.recv(1024))[0]
# 发送文件
send_file(file_path, exists_size)
def download(file_path):
# 封装下载信息
cmd_info = {'cmd': 'download', 'file_path': file_path}
# 序列化下载信息
cmd_info_picle = pickle.dumps(cmd_info)
# 发送下载信息
client.send(cmd_info_picle)
# 接收服务器返回响应
responses = pickle.loads(client.recv(1024))
print(responses)

# 判断文件是否存在
if 'code' in responses and responses['code'] == 1004:
print(responses['msg'])
else:
exists_size = os.path.getsize(responses['file_md5']) if os.path.exists(responses['file_md5']) else 0
client.send(struct.pack('i', exists_size))
# 接收文件
with open(responses['file_md5'], 'ab') as f:
while exists_size < responses['file_size']:
# 每次接收的数据
data = client.recv(1024)
# 累加到总接收大小
exists_size += len(data)
# 写入文件
f.write(data)
upload_progress(exists_size, responses['file_size'])
os.rename(responses['file_md5'], cmd_info['file_path'])
ret = pickle.loads(client.recv(1024))
print(ret)
if ret['code'] == 1001:
username = input('请输入用户名:')
pwd = input('请输入密码:')
client.send(pickle.dumps({'username':username, 'pwd':pwd}))
while 1:
print()
print('----------------------------------')
handle, file_path = input('请输入您要执行的命令:').split('|')
if handle == 'upload':
upload(file_path)
if handle == 'download':
download(file_path)

client.close()

后期整理以上代码