项目需求梳理
1. 用户登录认证(目前只实现一次连接一个客户端,即单线程)
1.1 用户密码 md5 加密
2. 查看目录
2.1 ls 命令 表示查看用户在服务端的目录,默认是家目录
2.2 dir 命令 表示用户查看在客户端的目录
3. 切换目录
3.1 用户使用 cd 命令切换服务端目录
3.1.1 实际上在服务端并没有真正切换目录
3.1.2 而且服务端代码编写时要注意,对用户来说,家目录就是最高一级,即对用户而言,home/username
是可以达到的路径,但 server/home 目录对用户来说是不存在路径
3.1.3 在切换路径后,客户端要记录用户此时切换到的路径,为接下来的 断点续存 功能做准备,不然会出现客
户端重新连接继续下载时找不到要下载文件在服务端路径的情况
4. 下载/上传/断点续存 进度条 创建
5. 下载功能
5.1 下载完成前,文件命名为 “filename.download”
5.1.1 在创建 “filename.download” 文件前判断该文件是否存在,如果存在,就在 download 后面再
加时间戳后缀
5.1.2 若 FTP 在文件下载完成前崩溃导致文件未下载完成,创建 shelvel 对象,记录 文件名,未下载完
成文件在服务端的路径,已下载的大小,为 断点续存 功能编写做准备
5.1.3 文件下载完成后,将后缀部分去除,如果去除后缀后文件已存在,就增加时间戳后缀,即“filename.时
间戳”
6. 上传功能(基本和下载功能一致)
6.1 下载完成前,文件命名为 “filename.upload”
6.1.1 在创建 “filename.upload” 文件前判断该文件是否存在,如果存在,就在 upload 后面再
加时间戳后缀
6.1.2 若 FTP 在文件上传完成前崩溃导致文件未上传完成,创建 shelvel 对象,记录 文件名,未上传完
成文件在服务端的路径,已下载的大小,为 断点续存 功能编写做准备
6.1.3 文件下载完成后,将后缀部分去除,如果去除后缀后文件已存在,就增加时间戳后缀,即“filename.时
间戳”
7. 下载/上传 断点续存功能
7.1 在用户登陆成功后,打印未下载完成、未上传完成的文件名,供用户选择是否要继续上传或下载
7.2 在用户选择继续下载或上传后,仍出现进度条并且从上次断的百分比开始增加
7.3 文件命名仍和下载/上传功能中文件命名一致
项目基本框架搭建
项目文件说明
-- FTP (项目名称)
-- client 客户端
-- server 服务端
-- bin (存放可执行的二进制文件)
-- conf (存放配置文件)
-- accounts.ini (包括用户名称和密码,在此文件中的密码使用 md5 加密,非明文显示)
-- setting.py (包括服务端接口、用户家目录等内容)
-- home(存放用户家目录,比如 home\aoteman 是 用户 aoteman 的家目录)
-- lib (存放的是函数调用的信息,在windows操作系统中起到链接程序和函数的作用。其意义在于代码重用,程
序员将常用的功能写成函数,保存为lib文件,在以后编程要实现这些功能的时候,就不需要再重新编写代
码,而是直接调用写好的lib文件,这很大程度上减轻程序员的负担。)
-- log (项目日志)
bin 文件夹下 FTPServer.py 文件
import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
if __name__ == '__main__':
from lib import management
FTP = management.FTPManagement(sys.argv)
FTP.execute()
lib 文件夹下 management.py
from lib import main
class FTPManagement(object):
def __init__(self, sys_argv):
self.sys_argv = sys_argv
def help_msg(self):
"""
帮助信息,为用户打印提示信息
"""
help_msg = """
start start up server
"""
exit(help_msg)
def parameter_verification(self):
"""
验证参数合法性
1. 项目启动时的命令设置为 python 文件路径 命令(如 start)
1.1 所以如果参数长度小于 2,应该向用户提示服务端可以接受的,命令
1.2 只需要判断长度是否小于2,如果大于2,程序只把 sys.argv 列表中的第二位参数视为命令
"""
if len(self.sys_argv) < 2:
self.help_msg()
def execute(self):
"""解析指令"""
self.parameter_verification()
parameter = self.sys_argv[1]
if hasattr(self, parameter):
func = getattr(self, parameter)
func()
else:
print(" command does not exist !")
print(" start start up server !")
def start(self):
Server = main.Server(self)
Server.run_forever()
lib 文件夹下 main.py
import socket
from conf import setting
class Server(object):
MSG_SIZE = 8192
STATUS_CODE = {
}
def __init__(self, management):
self.management = management
self.user_home = setting.USER_HOME_DIR
self.current_dir = self.user_home
self.shelve_obj = None
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((setting.HOST, setting.PORT))
self.sock.listen(5)
def run_forever(self):
"""正式启动服务端"""
print(("FTP Server start %s %s " % (setting.HOST, setting.PORT)).center(50, "-"))
while True:
self.conn, self.addr = self.sock.accept()
print(self.addr)
print("Connection from %s" % (self.addr, ))
try:
self.handle()
except Exception as e:
print(e)
def handle(self):
pass
conf 文件夹下 setting.py 文件
import os.path
HOST = "0.0.0.0"
PORT = 9999
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
USER_HOME_DIR = os.path.join(BASE_DIR, "home")
client 文件夹下 FTPClient.py 文件
import optparse
import shelve
import socket
class Client(object):
MSG_SIZE = 8192
def __init__(self):
self.show_to_client = None
self.username = None
self.current_dir = None
self.shelve_obj = shelve.open("FTP_Download")
self.shelve_obj = shelve.open("FTP_Upload")
parser = optparse.OptionParser()
parser.add_option("-H", "--serverHost", dest="serverHost", help="ftp server ip_addr")
parser.add_option("-P", "--port", type="int", dest="port", help="ftp server port")
self.options, self.args = parser.parse_args()
self.parameter_test()
self.make_connection()
def parameter_test(self):
"""参数检验,保证用户输入了 -H 和 -P 内容,不然客户端无法连接服务端端口"""
if not self.options.serverHost or not self.options.port:
exit("Error: must supply serverHost and port !")
def make_connection(self):
"""客户端连接"""
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect((self.options.serverHost, self.options.port))
print("Connection successful !")
if __name__ == '__main__':
client = Client()
当前运行结果(终端运行)
服务端
客户端