基于套接字通信的简单练习(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源码的小伙伴,关注我博客,下方评论。送上源码。。。

 

 

 






posted @ 2018-06-10 18:20  rianley  阅读(333)  评论(2编辑  收藏  举报