基于套接字通信的简单练习(FTP)
本项目基于c/s架构开发(采用套接字通信,使用TCP协议)
FTP-Socket
"""
__author:rianley cheng
"""
功能说明:
本程序是一个模拟 FTP 的应用,包括客户端和服务端,实现如下功能:
1 可以实现多客户端连接, 服务端采用 SocketServer 模块实现,支持多客户端连接
2 实现客户端登录验证, 对客户端登录时采用 sha224 加密算法进行加密,
3 对用户访问目录进行限制,只允许在自己家目录下进行访问,不能进入其他用户目录
4 对用户上传目录磁盘进行限制,支持不同用户定义不同大小,默认500M (此处有服务端进行分配)
5 用户注册放到了服务端进行(客户端无法注册用户)
6 支持上传文件夹 and 文件
7 文件校验比对(主要是 将文件的名称,大小 进行混淆,来确定文件收发的完整性)
8 支持文件上传、下载的进度显示
9 支持以下命令功能
put: 上传文件
get: 下载文件
show: 显示文件夹内容
cd: 目录切换
10 支持断点续传,分片上传。(同时间只有一个·生成器·在内存(客户端 服务端都是如此)) ,不断的收发数据 (优化内存)
11 全局日志记录
目录介绍:
client:
|--start.py (主引导程序文件)
|
|--bin (主接口文件目录)
| |-- ftpclient.py (主接口文件,登录接口、调用命令模块主文件)
|
|--conf (配置文件目录)
| |-- codes.py (状态码文件)
| |-- settings.py (系统配置主文件)
| |-- tempate.py (模板文件)
|
|--download (文件下载存放目录)
|
|--logs (日志目录)
| |-- ftpclient.log
|
|--module (模块目录)
| |-- common.py (公共模块)
| |-- client.py (客户端类文件,定义客户端所有命令的方法)
-----------------------------------------------------------------------------------
servr:
|--start.py (主引导程序文件)
|
|--bin (主接口文件目录)
| |-- ftpserver.py (主接口文件,登录接口、调用命令模块主文件)
|
|--conf (配置文件目录)
| |-- settings.py (系统配置主文件)
| |-- tempate.py (模板文件)
|
|-- database (数据保存文件)
| |-- breakpoint (断点续传记录文件保存目录,每个用户一个文件)
| |-- user.ini (用户信息保存文件)
|
|-- dbhelper
| |-- dbapi.py (数据操作接口)
|
|--upload (用户上传文件存放目录,下面存放所有用户的家目录,一个用户一个文件夹)
|
|--logs (日志目录)
| |-- ftpclient.log
|
|--module (模块目录)
| |-- common.py (公共模块)
| |-- server.py (服务端模块文件,定义服务端所有命令的方法)
| |-- users.py (用户类文件,每个客户端连接后会生成一个用户对象,这个类文件用来实例化用户对象)
应用关键模块及知识点:
1 模块: socket, socketserver, hashlib(md5,sha224),mutilprocess
2 知识点: 多进程、反射、类、套接字通信、模块的综合使用
项目主要接口关系图:
项目运行截图:
项目核心点介绍以及出现的问题介绍:
第一版,文件夹上传,原本是通过遍历文件中子文件,子子文件,将文件路径存一个列表,将具体文件存一个列表 先传文件夹 (在服务端把具体的文件夹层次关系建立好!)在传文件 将文件内容拿到 往他们对应的文件夹中写入。
但是 出了一个bug(暂无解决) 有的时候 可以 有的时候不行。于是我换用了方式二:
第二版 文件夹上传 采用打包的方式(将文件夹打包,传文件包过去),服务端在接收到文件包后,采用解压文件包的形式。客户端在数据传输完毕 ,删除打包文件,服务端解压文件包完成后。删除文件包。(判断标准是文件夹上传带一个参数is_file ='1',来标识,用户上传的是文件夹还是文件!)
断点续传:采用seek方式 在服务端接收数据 出现客户端异常中断! 将保留原始数据,且记录该用户续传的信息
def write_breakpoint(filemd5, filesize, recved_size, filepath, userobj): """ 写断点文件信息,每个用户的断点文件信息保存在各自用户的家目录下 :param filesize: 文件总大小 :param filemd5: 文件的MD5值 :param recved_size: 已接收大小 :param filepath: 文件存放路径,全名 ex: uploads/test/aaa.txt :param userobj: 客户端用户对象 :return: 写文件,格式为{"filemd5":{"filepath":filepath,"recvsize":recved_size,"totalsize":totalsize}} """ breadpoint_file = os.path.join(settings.BREAK_POINT_FOLDER, userobj.username) bfile = {filemd5: {"filepath": filepath, "totalsize": filesize, "recvsize": recved_size}} # 将信息写入文件 with open(breadpoint_file, 'w+') as fw: fw.write(json.dumps(bfile))
服务端记录之后:
客户端在上传同名文件,会先传头部信息。服务端验证,是否存在续传,如果存在,返回一个seek值。
客户端在传的时候 ,会判断是否存在续传,存在提示用户,该文件断点续传,在发送数据时 则在传的过程中加上服务端续传过来的位置!seek让文件光标跳转到已上传位置,进行续传
if str(ack_by_server, encoding='utf-8').split("|")[0] == "0": # 新文件 sended_size = 0 # 判断文件是否超过磁盘配额空间 if self.usedspace + fsize > self.totalspace: return "超过磁盘配额,无法上传文件,请联系管理员!" else: self.usedspace += fsize else: sended_size = int(str(ack_by_server, encoding='utf-8').split("|")[1]) print("此文件已发送过,但未发送完,开始断点续传!......") # 开始发送文件 try: with open(file_name, 'rb') as f: f.seek(sended_size) #服务端传过来断点续传文件的值(如果该文件没有存在断点续传,该值为零,则不是断点续传文件) while fsize - sended_size > 2048: contant = f.read(2048) r_size = len(contant) self.socket.send(contant) sended_size += r_size #调用进度条函数 common.print_process(fsize, sended_size) else: contant = f.read(fsize - sended_size) self.socket.send(contant) #调用进度条函数 common.print_process(fsize, fsize) common.writelog("upload file<{0}> successful".format(file_name), "info") return "\n文件发送成功"
写的比较low,需要FTP源码的小伙伴,关注我博客,下方评论。送上源码。。。