作业:开发一直支持多用户在线的FTP程序
要求:
1.用户加密认证
2.允许同时多用户登录
3.每个用户有自己家目录,且只能访问自己的家目录
4.对用户进行磁盘配额,每个用户的可用空间不同
5.允许用户在ftp server上随意切换目录
6.允许用户查看当前目录下文件
7.允许上传和下载,保证文件一致性
8.文件传输过程中显示进度条
9.附加功能:支持文件的断点续传
作业:开发一个支持多用户在线的FTP程序
要求:
用户加密认证
允许同时多用户登录
每个用户有自己的家目录,且只能访问自己的家目录
对用户进行磁盘配额,每个用户的可用空间不同
允许用户在ftp server上随意切换目录
允许用户查看当前目录下文件
执行命令:
允许上传和下载文件,保证文件一致性
文件传输过程中显示进度条
附加功能:支持文件断点续传
3004 未授权
3014 已授权
1001 命令粘包命令
class Action:
def __init__(self):
self.is_login = False
self.prev_home = '/User/Data'
self.name = None #alex
#/User/Data/alex
xiaohu = Action() #/User/Data/xiaohu
ls /User/Data/xiaohu
jinxin = Action() #/user/data/jinxin
ls /user/data/jinxin
obj1 = Action()
obj2 = Action()
os.cd(xiaohu.home)
结果 = subprocess.getoutput('ls xiaohu.home')
EasyServerFTP目录结构
smoke@smoke-GS70-2PC-Stealth:~/文档/DocumentFile/PycharmProjects/pythonProject$ tree EasyServerFTP/
EasyServerFTP/
├── bin
│ ├── __init__.py
│ └── program.py
├── config
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── __pycache__
│ │ ├── __init__.cpython-35.pyc
│ │ └── settings.cpython-35.pyc
│ ├── settings.py
│ └── settings.pyc
├── home
│ ├── alex
│ ├── eric
│ └── wupeiqi
│ ├── hello
│ │ └── h
│ └── tyrion11.gif
├── log
│ └── __init__.py
└── src
├── __init__.py
├── __init__.pyc
├── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── service.cpython-35.pyc
└── service.py
12 directories, 15 files
bin目录
program.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from src import service
if __name__ == "__main__":
service.MultiServer()
config目录
settings.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BIND_HOST = '127.0.0.1'
BIND_PORT = 9992
USER_HOME = os.path.join(BASE_DIR, 'home')
USER_ACCOUNT = {
'alex': {
'password': 'alex123',
'quotation': 1000000,
'expire': '2016-01-22'
},
'rain': {
'password': 'rain123',
'quotation': 2000000,
'expire': '2016-01-22'
},
}
home目录
log目录
src目录
service.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import subprocess
import re
import os
import json
import socketserver
from config import settings
ACTION_CODE = {
'1000': 'cmd',
'2000': 'post',
'3000': 'get',
}
REQUEST_CODE = {
'1001': 'cmd info',
'1002': 'cmd ack',
'2001': 'post info',
'2002': 'ACK(可以开始上传)',
'2003': '文件已经存在',
'2004': '续传',
'2005': '不续传',
'3001': 'get info',
'3002': 'get ack',
'4001': "未授权",
'4002': "授权成功",
'4003': "授权失败"
}
class Server(object):
request_queue_size = 5
def __init__(self):
self.socket = socket.socket()
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.server_bind(settings.BIND_HOST, settings.BIND_PORT)
self.server_activate()
except Exception as e:
print(e)
self.server_close()
def server_bind(self, ip, port):
self.socket.bind((ip, port,))
def server_activate(self):
self.socket.listen(self.request_queue_size)
self.run()
def server_close(self):
self.socket.close()
def run(self):
# get request
while True:
conn, address = self.socket.accept()
conn.sendall(bytes("欢迎登陆", 'utf-8'))
obj = Action(conn)
while True:
client_bytes = conn.recv(1024)
if not client_bytes:
break
client_str = str(client_bytes, encoding='utf-8')
if obj.has_login:
o = client_str.split('|', 1)
if len(o) > 0:
func = getattr(obj, o[0])
func(client_str)
else:
conn.sendall(bytes('输入格式错误', 'utf-8'))
else:
obj.login(client_str)
conn.close()
class MultiServerHandler(socketserver.BaseRequestHandler):
def handle(self):
conn = self.request
conn.sendall(bytes("欢迎登陆", 'utf-8'))
obj = Action(conn)
# self.conn = conn
# self.has_login = False
# self.username = None
# self.home = None
# self.current_dir = None
while True:
client_bytes = conn.recv(1024)
if not client_bytes:
break
# cmd|ipconfig
client_str = str(client_bytes, encoding='utf-8')
if obj.has_login:
o = client_str.split('|', 1)
# o[0]: cmd
if len(o) > 0:
func = getattr(obj, o[0])
func(client_str) # cmd|ipconfig
else:
conn.sendall(bytes('输入格式错误', 'utf-8'))
else:
obj.login(client_str)
conn.close()
class MultiServer(object):
def __init__(self):
server = socketserver.ThreadingTCPServer((settings.BIND_HOST, settings.BIND_PORT), MultiServerHandler)
server.serve_forever()
class Action(object):
def __init__(self, conn):
self.conn = conn
self.has_login = False
self.username = None
self.home = None
self.current_dir = None
def login(self, origin):
self.conn.sendall(bytes("4001", 'utf-8'))
while True:
login_str = str(self.conn.recv(1024), encoding='utf-8')
login_dict = json.loads(login_str)
if login_dict['username'] == 'wupeiqi' and login_dict['pwd'] == '123':
self.conn.sendall(bytes("4002", 'utf-8'))
self.has_login = True
self.username = 'wupeiqi'
self.initialize()
break
else:
self.conn.sendall(bytes("4003", 'utf-8'))
def initialize(self):
self.home = os.path.join(settings.USER_HOME, self.username)
self.current_dir = os.path.join(settings.USER_HOME, self.username)
def cmd(self, origin):
func, command = origin.split('|', 1)
command_list = re.split('\s*', command, 1)
if command_list[0] == 'ls':
if len(command_list) == 1:
if self.current_dir:
command_list.append(self.current_dir)
else:
command_list.append(self.home)
else:
if self.current_dir:
p = os.path.join(self.current_dir, command_list[1])
else:
p = os.path.join(self.home, command_list[1])
command_list[1] = p
if command_list[0] == 'cd':
if len(command_list) == 1:
command_list.append(self.home)
else:
if self.current_dir:
p = os.path.join(self.current_dir, command_list[1])
else:
p = os.path.join(self.home, command_list[1])
self.current_dir = p
command_list[1] = p
command = ' '.join(command_list)
try:
result_bytes = subprocess.check_output(command, shell=True)
# result_bytes # gbk字节
result_bytes = bytes(str(result_bytes, encoding='gbk'), encoding='utf-8')
except Exception as e:
result_bytes = bytes('error cmd', encoding='utf-8')
info_str = "info|%d" % len(result_bytes)
self.conn.sendall(bytes(info_str, 'utf-8'))
ack = self.conn.recv(1024)
self.conn.sendall(result_bytes)
def post(self, origin):
func, file_byte_size, file_name, file_md5, target_path = origin.split('|', 4)
target_abs_md5_path = os.path.join(self.home, target_path)
has_received = 0
file_byte_size = int(file_byte_size)
if os.path.exists(target_abs_md5_path):
self.conn.sendall(bytes('2003', 'utf-8'))
is_continue = str(self.conn.recv(1024), 'utf-8')
if is_continue == "2004":
has_file_size = os.stat(target_abs_md5_path).st_size
self.conn.sendall(bytes(str(has_file_size), 'utf-8'))
has_received += has_file_size
f = open(target_abs_md5_path, 'ab')
else:
f = open(target_abs_md5_path, 'wb')
else:
self.conn.sendall(bytes('2002', 'utf-8'))
f = open(target_abs_md5_path, 'wb')
while file_byte_size > has_received:
data = self.conn.recv(1024)
f.write(data)
has_received += len(data)
f.close()
def get(self, origin):
pass
def exit(self, origin):
pass
EasyClientFTP目录结构
smoke@smoke-GS70-2PC-Stealth:~/文档/DocumentFile/PycharmProjects/pythonProject$ tree EasyClientFTP/
EasyClientFTP/
├── bin
│ ├── __init__.py
│ └── program.py
├── config
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-35.pyc
│ │ └── settings.cpython-35.pyc
│ └── settings.py
├── lib
│ ├── commons.py
│ ├── __init__.py
│ └── __pycache__
│ ├── commons.cpython-35.pyc
│ └── __init__.cpython-35.pyc
├── log
│ └── __init__.py
└── src
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── service.cpython-35.pyc
└── service.py
8 directories, 15 files
bin目录
program.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from src import service
if __name__ == '__main__':
service.main()
config目录
settings.py
#!/usr/bin/env python # -*- coding:utf-8 -*- server = '127.0.0.1' port = 9992
lib目录
commons.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import hashlib
def fetch_file_md5(file_path):
obj = hashlib.md5()
f = open(file_path, 'rb')
while True:
b = f.read(8096)
if not b:
break
obj.update(b)
f.close()
return obj.hexdigest()
def bar(num=1, total=100):
rate = float(num) / float(total)
rate_num = int(rate * 100)
temp = '\r%d %%' % (rate_num, )
sys.stdout.write(temp)
sys.stdout.flush()
log目录
src目录
service.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import os
import re
import json
from config import settings
from lib import commons
def login(conn):
while True:
username = input('请输入用户名(q退出):')
pwd = input('请输入密码:')
login_info = {'username': username, 'pwd': pwd}
conn.sendall(bytes(json.dumps(login_info), 'utf-8'))
if str(conn.recv(1024), encoding='utf-8') == "4002":
print('授权成功...')
break
else:
print('用户名或密码错误...')
def cmd(conn, inp):
conn.sendall(bytes(inp, 'utf-8'))
basic_info_bytes = conn.recv(1024)
basic_info_str = str(basic_info_bytes, 'utf-8')
if basic_info_str == '4001':
login(conn)
else:
conn.sendall(bytes("ack", 'utf-8'))
print(str(basic_info_bytes, 'utf-8'))
result_length = int(str(basic_info_bytes, 'utf-8').split('|')[1])
has_received = 0
content_bytes = bytes()
while has_received < result_length:
fetch_bytes = conn.recv(1024)
has_received += len(fetch_bytes)
content_bytes += fetch_bytes
cmd_result = str(content_bytes, 'utf-8')
print(cmd_result)
def post(conn, inp):
# inp ===> post|D:/1.txt 2.txt
method, file_paths = inp.split("|", 1)
local_path, target_path = re.split('\s*', file_paths, 1)
# 获取本地文件大小
file_byte_size = os.stat(local_path).st_size
# 获取本地文件名
file_name = os.path.basename(local_path)
# 获取本地文件md5值
file_md5 = commons.fetch_file_md5(local_path)
post_info = "post|%s|%s|%s|%s" % (file_byte_size, file_name, file_md5, target_path)
conn.sendall(bytes(post_info, 'utf-8'))
result_exist = str(conn.recv(1024), 'utf-8')
if result_exist == '4001':
login(conn)
return
has_sent = 0
if result_exist == "2003":
inp_continue = input('文件已经存在,是否续传?Y/N')
if inp_continue.upper() == "Y":
conn.sendall(bytes('2004', 'utf-8'))
result_continue_pos = str(conn.recv(1024), 'utf-8')
has_sent = int(result_continue_pos)
else:
conn.sendall(bytes('2005', 'utf-8'))
file_obj = open(local_path, 'rb')
file_obj.seek(has_sent)
while file_byte_size > has_sent:
data = file_obj.read(1024)
conn.sendall(data)
has_sent += len(data)
commons.bar(has_sent, file_byte_size)
file_obj.close()
print('上传成功')
def get(conn, inp):
pass
def help_info():
print("""
cmd|命令
post|文件路径
get|下载文件路径
exit|退出
""")
def execute(conn):
choice_dict = {
'cmd': cmd,
'get': get,
'post': post,
}
help_info()
while True:
# cmd|ls
# post|本地路径 服务器上路径
# get|服务器路径 本地路径
inp = input("please input:")
if inp == 'help':
help_info()
continue
choice = inp.split('|')[0]
if choice == 'exit':
return
if choice in choice_dict:
func = choice_dict[choice]
func(conn, inp)
def main():
ip_port = (settings.server, settings.port)
conn = socket.socket()
conn.connect(ip_port)
welcome_bytes = conn.recv(1024)
print(str(welcome_bytes, encoding='utf-8'))
execute(conn)
conn.close()
运行EasyServerFTP
/home/smoke/文档/DocumentFile/PycharmProjects/EasyServerFTP/venv/bin/python /home/smoke/文档/DocumentFile/PycharmProjects/EasyServerFTP/bin/program.py
运行EasyClientFTP
/home/smoke/文档/DocumentFile/PycharmProjects/EasyClientFTP/venv/bin/python /home/smoke/文档/DocumentFile/PycharmProjects/EasyClientFTP/bin/program.py
欢迎登陆
cmd|命令
post|文件路径
get|下载文件路径
exit|退出
please input:cmd|ls
请输入用户名(q退出):wupeiqi
请输入密码:123
授权成功...
please input:cmd|ip addr show
info|867
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp4s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
link/ether 44:8a:5b:f1:bd:c5 brd ff:ff:ff:ff:ff:ff
3: wlp5s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d8:fc:93:0f:85:93 brd ff:ff:ff:ff:ff:ff
inet 192.168.254.15/27 brd 192.168.254.31 scope global dynamic noprefixroute wlp5s0
valid_lft 6872sec preferred_lft 6872sec
inet6 fe80::8df3:c59b:ccd4:56b2/64 scope link noprefixroute
valid_lft forever preferred_lft forever
please input:
浙公网安备 33010602011771号