Python FTP 服务端项目详细功能文档

项目概述

该 FTP 服务端与配套 FTP 客户端成对使用,基于 socketserver 多线程 TCP 网络框架实现,支持多客户端并发连接。服务端采用 ini 配置文件数据库 存储用户账号信息,使用 JSON 文件保存断点续传记录;实现用户账号管理、登录认证、磁盘配额管控、文件上传(断点续传)、文件下载、目录浏览、目录切换、日志记录等全套 FTP 服务能力。

服务端会启动独立子进程运行 TCP 服务,每一个客户端连接分配独立线程处理请求;客户端与服务端通信协议格式为:命令|参数1|参数2...,以管道符 | 分隔字符串。
项目文件清单

文件路径 文件名称 模块作用
ftpserver.py ftpserver.py 服务端主程序,进程管理、TCP 服务入口、请求分发 Handler
conf/settings.py settings.py 服务端全局配置:IP 端口、上传目录、日志、用户数据库、断点目录、默认磁盘配额
conf/template.py template.py 服务端本地控制台菜单模板(管理员后台菜单)
modules/common.py common.py 公共工具:sha224 密码加密、md5 文件校验、日志、输入校验
modules/users.py users.py Users 用户业务类:用户实例、认证、家目录、磁盘配额更新、创建用户
modules/server.py server.py FTP 业务命令实现:auth 登录、put 上传、get 下载、show 查看目录、cd 切换目录
dbhelper/dbapi.py dbapi.py 文件数据库操作 API:ini 用户配置读写、断点 JSON 文件增删改查

1. conf/settings.py 服务端配置模块

功能说明

统一管理服务端运行参数、各类存储路径,自动解析项目根目录,路径动态拼接。

配置项 说明
FTP_SERVER_IP 监听 IP,默认 127.0.0.1
FTP_SERVER_PORT 监听 TCP 端口,默认 9999,客户端需要与此端口一致
DOWNLOAD_FILE_PATH 服务端接收上传文件根目录,实际用户家目录存放于此
LOGS 服务端日志路径 logs/ftpserver.log,记录连接、命令、异常
USER_INFO 用户 ini 配置数据库存放目录
USER_HOME_FOLDER 所有用户家目录父目录,每个用户在此生成独立文件夹
HOME_QUOTA 新建用户默认磁盘配额,单位 MB
BREAK_POINT_FOLDER 断点续传记录存储目录,每个用户单独一个 json 断点文件

说明:每个登录用户会在 USER_HOME_FOLDER 下生成同名文件夹作为用户家目录,用户无法跳出家目录访问其他用户数据。


2. conf/template.py UI 模板模块

存放服务端本地管理员控制台打印模板。

  1. MENU_START:服务端启动首页模板

  2. MENU_USER_CENTER:管理员用户管理菜单:查看用户、新增用户、锁定用户

注意:该模板用于 服务端本机控制台交互,和客户端无关;客户端交互菜单在客户端的 template.py 中。


3. modules/common.py 公共工具模块

与客户端公共工具函数功能基本对齐,保证两端加密算法一致。

3.1 encry_sha(string)

sha224 加密明文密码。客户端上传加密后字符串,服务端数据库存储加密后的摘要。

3.2 encry_md5(file)

分块读取文件计算 md5 哈希值。下载文件时,服务端计算文件 md5 下发给客户端,客户端做完整性校验。

3.3 writelog(content, types)

写日志,自动拼接时间、日志级别 info/error,追加写入 ftpserver.log。

3.4 input_msg(message, limit_value=tuple(), default="")

管理员控制台输入工具;支持空输入判断、限定可选输入、设置默认值。


4. dbhelper/dbapi.py 文件数据库操作模块

底层存储:用户账号使用 configparser 读写 ini 配置文件;断点续传记录使用 JSON 文件存储。

用户 ini 配置字段说明

每个用户对应 ini 中的一个 section,key 包含:

  • password:sha224 密文

  • islocked:1 锁定 / 0 正常

  • isdel:1 删除 / 0 正常

  • totalspace:总空间字节数

  • usedspace:已使用字节数

4.1 read_section_all()

读取 ini 全部 section,返回全部用户名列表。

4.2 read_section_by_name(section_name)

根据用户名读取单个用户全部配置,返回字典。

4.3 add_option(section_name, **kwargs)

新增用户,写入 ini 配置。

4.4 modify_option(section_name, **kwargs)

修改用户信息:密码、锁定状态、删除标记、磁盘总空间、已使用空间,覆盖写回 ini 文件。

断点续传 JSON 相关函数

断点文件存放路径:BREAK_POINT_FOLDER/用户名,json 结构:{md5 值: {"filepath": "文件路径", "totalsize": 总大小, "recvsize": 已接收字节}}

4.5 write_breakpoint(filemd5, filesize, recved_size, filepath, userobj)

当客户端上传中途断开,写入断点记录,保存文件 md5、总大小、已经接收字节、文件保存路径。

4.6 check_breakpoint(filemd5, userobj)

根据用户对象 + 文件 md5 查询是否存在断点记录:

  • 存在:返回 [已接收字节, 文件路径]

  • 不存在:返回 [0, 用户当前工作目录]

4.7 del_breakpoint(filemd5, userobj)

文件完整上传完成后,删除该 md5 对应的断点记录,清除续传标记。


5. modules/users.py Users 用户类

每一个登录客户端实例化一个 Users 对象,保存该登录用户全部状态。

对象属性

属性 含义
username 用户名
exists 布尔,用户是否存在数据库
password sha224 加密密码
islocked 0 正常 / 1 账户锁定
isdel 0 未删除 / 1 已删除
totalspace 用户总磁盘配额,字节单位
usedspace 已经使用磁盘大小,字节单位
homepath 用户家目录绝对路径,登录后不可跳出该目录
currpath 用户当前工作目录,执行 cd 会修改此属性

方法详情

5.1 init(username)

构造函数:初始化全部属性,调用 __check_users() 检查用户是否存在。

5.2 __check_users()

读取 ini 用户列表,如果用户名存在,设置 exists=True,加载用户信息,初始化当前路径为家目录。

5.3 __load_user_info()

读取 ini 配置,把数据库用户信息赋值到对象属性。

5.4 user_auth(password)

登录密码校验:传入 sha224 加密密码,和对象 password 比对;账户锁定 / 删除直接返回 False。返回 True=认证成功,False 认证失败。

5.5 create_user()

新建用户:调用 dbapi 写入 ini 配置,调用 __create_folder() 创建用户家目录文件夹。

5.6 __create_folder()

创建用户专属家目录文件夹。

5.7 update_quota(filesize)

上传新文件后更新 usedspace += filesize,调用 dbapi 同步修改 ini 配置文件的已用空间。


6. modules/server.py FTP 业务命令处理模块

接收来自 ftpserver.py 分发的网络请求,实现各个 FTP 命令业务逻辑。

6.1 auth(client_socket, args) 用户登录认证

入参 args 格式字符串:auth|用户名|sha224密码

  1. 分割解析用户名和加密密码,实例化 Users 对象

  2. 判断用户是否存在,账户是否锁定、删除,密码是否匹配

  3. 返回状态码给 socket:

返回码 含义
"0" 认证成功;继续发送 `总字节
"1" 用户不存在 / 已删除
"2" 账户被锁定
"3" 用户名密码错误
  1. 返回用户对象 client_user 给上层 handler。

6.2 show(client_socket, client_user, recv_data) 查看目录列表

命令:show|

  1. 获取用户对象 currpath 当前工作目录;os.listdir() 列出目录全部内容。

  2. 遍历目录,区分文件 / 文件夹;获取修改时间、文件大小,组装返回字符串。返回格式:文件总数量|文件列表文本

  3. 通信协议流程

    1. 先把返回字符串编码后的字节长度发送给客户端

    2. 等待客户端回复 ready

    3. 发送完整目录文本数据

  4. 客户端收到总长度后循环接收字节,避免 TCP 粘包问题。

6.3 cd(client_socket, client_user, recv_data) 切换目录

命令格式 cd|目录名,支持 cd|.. 返回上级目录。

逻辑:

  1. 如果参数是 ..:

    1. 当前路径等于用户家目录:返回状态码 0|目录名(提示已经根目录)

    2. 不是家目录:修改 client_user.currpath 为父目录,返回状态码 1|目录名 切换成功

  2. 如果普通文件夹名称:拼接完整路径;判断是否文件夹

    1. 是文件夹:修改 currpath 属性,返回 1|文件夹名

    2. 不是文件夹,返回 2|文件夹名 切换失败

  3. 发送结果字符串 状态码|目录名称 给客户端。

限制:用户无法切换到自己 homepath 上级目录,保证安全隔离。

6.4 put(client_socket, client_user, recv_data) 文件上传接收

客户端发送命令元数据格式:put|文件名|filesize|filemd5

  1. 解析文件名、文件总大小、文件 md5;调用 dbapi.check_breakpoint() 查询断点记录。

  2. 无断点:break_status=0 recv_size=0,保存路径为当前工作目录,调用 client_user.update_quota() 累加占用磁盘。

  3. 有断点:break_status=1 recv_size=已经接收字节,沿用上次未传完文件路径。

  4. 向客户端返回应答:状态码|已接收字节,客户端据此执行全新上传或者 seek 断点续传。

  5. 以追加二进制模式打开文件,seek 跳转到已接收偏移,循环每次接收 2048 字节写入本地。

  6. 异常处理:客户端断开、发生异常,把当前已经接收大小写入断点记录。

  7. 文件全部接收完毕:如果是断点续传,删除断点 json 记录。

6.5 get(client_socket, client_user, recv_data) 文件下载发送

命令格式 get|文件名

  1. 拼接路径:currpath + filename,判断文件是否存在

    1. 文件不存在:发送 0 标识给客户端,结束

    2. 文件存在:发送 1 标识告知客户端文件存在

  2. 等待客户端回复 ok;获取文件大小,计算本地 md5,发送字符串 filesize|filename|md5

  3. 等待客户端发送 ready;分块读取本地文件,每次 2048 字节发送二进制数据给客户端。

md5 由服务端计算下发,客户端下载完成后本地计算 md5 做完整性比对。


7. ftpserver.py 服务端主程序入口

7.1 Myserver(socketserver.BaseRequestHandler) 请求处理类

每一个新 TCP 连接,实例化 Myserver 对象,handle 方法在独立线程运行。

handle() 执行流程:

  1. 获取连接 socket 对象 self.request、客户端地址 client_address,写入日志记录客户端接入。

  2. 向客户端发送 OK 字符串(客户端 connect 等待这个 OK,作为连接成功标记)。

  3. 定义变量 client_user = None,代表当前连接登录用户对象,未登录为 None。

  4. while 死循环持续接收客户端命令(TCP 长连接):

    1. recv 读取 100 字节命令头;收到空字节代表客户端断开连接,关闭 socket 退出循环。

    2. 分割字符串取出命令 cmd(auth / put / get / show / cd)。

    3. 如果 cmd == auth:调用 server.auth() 执行登录,返回赋值 client_user 用户对象。

    4. 其他业务命令:必须用户登录成功,使用 反射 getattr(server, cmd) 调用 server 模块对应业务函数,传入 socket、用户对象、原始命令字符串。

    5. 捕获执行异常,记录 error 日志,关闭 socket 连接。

反射设计:新增业务命令,只需要在 server.py 新增函数,不需要修改主 handler 循环。

7.2 doprocess()

初始化 ThreadingTCPServer 多线程 TCP 服务,绑定 IP 端口,调用 serve_forever() 永久监听。

ThreadingTCPServer:每一个客户端连接生成独立线程,实现多用户并发。

7.3 start()

创建 Process 子进程运行 doprocess,返回进程对象。

服务运行在独立子进程,不和管理员控制台主线程抢占。


整体通信交互完整流程(客户端 ↔ 服务端)

  1. 客户端 socket 发起 TCP 连接;服务端 accept,新开线程,回复 OK。客户端收到 OK → 连接成功。

  2. 客户端输入账号密码,密码 sha224 加密,发送 auth|user|sha224passwd。

  3. 服务端执行 auth 认证,返回状态码;认证成功下发 总空间|已用空间 字节数据。客户端保存用户空间信息。

  4. 登录完成,进入客户端交互循环。

    1. put 上传 客户端发送元数据 put|name|size|md5 服务端查询断点,回复 0|0 新文件 / 1|已收字节 断点续传 客户端 seek 偏移发送二进制文件;服务端接收;中途断开写入断点;全部完成清除断点。

    2. get 下载 客户端发送 get|filename;服务端判断文件是否存在返回 1/0。 文件存在:服务端发送 size|name|md5;客户端回复 ready;服务端分块发送文件二进制流;客户端本地保存,计算 md5 校验。

    3. show 查看目录 客户端发送 show|;服务端发送返回字符串总长度;客户端回复 ready;服务端传输目录文本。

    4. cd 切换目录 客户端发送 cd|xxx;服务端修改用户 currpath,返回切换结果。

  5. 客户端输入 quit 退出客户端;服务端 recv 得到 b'',记录客户端断开日志,关闭 socket。


关键技术点

  1. 多客户端并发 :socketserver.ThreadingTCPServer,每个连接独立线程。

  2. 进程隔离:FTP TCP 服务运行在独立子进程,管理员控制台为主进程。

  3. 用户隔离:每个用户独立家目录,cd 命令不能跳出家目录,不同用户文件互相隔离。

  4. 磁盘配额管控:服务端 Users 类记录总空间与已使用空间;上传新文件自动更新 ini 数据库。

  5. 断点续传:md5 作为文件唯一标识,JSON 记录断点;客户端断开下次上传同一个 md5 文件自动恢复。

  6. 完整性校验:服务端计算文件 md5 下发给客户端,下载后客户端校验,检测传输损坏。

  7. 反射调用:命令字符串直接映射函数,业务命令解耦。

  8. 文件数据库:configparser ini 存储账号;JSON 存储断点;不依赖 MySQL 等外部数据库。

  9. 日志系统:客户端连接断开、每条执行命令、异常全部持久化日志。


现存缺陷与改进建议

  1. socket recv(固定字节) 存在 TCP 粘包风险,协议没有消息边界,大数据高并发场景容易解析出错。

  2. 断点文件逻辑:同一 md5 多个文件会冲突;每个用户只保存一条断点记录。

  3. 上传时磁盘配额 仅客户端做前置校验,服务端 put 虽然更新配额,但没有二次校验,存在超配额上传风险。

  4. 没有实现主动关闭 socket;客户端 quit 退出,服务端依靠 recv 空字节感知断开。

  5. dbapi 中 check_breakpoint 函数调用 common.write(),源码方法名错误,应为 common.writelog(),运行会抛异常。

  6. ini 数据库并发写:多个线程同时修改同一个用户配置,会出现 ini 配置文件内容错乱。

  7. 用户删除标记 isdel 仅有判断,没有提供完整删除文件目录逻辑。

相关推荐
一只爱撸猫的程序猿1 小时前
全球出货第二,却不在 AI 的默认推荐里:一份悖论解剖报告
人工智能·python·产品
半杯咖啡半行码1 小时前
QML进阶实战(二):异步网络、性能优化与工程化落地
网络·qt·性能优化·qml
少年的范儿1 小时前
Windows 安装 MySQL 8.4 教程(ZIP 解压版):配置 my.ini、初始化数据库与启动服务
数据库·windows·mysql
miofly1 小时前
GitHub 今日推荐|Edge0:让大号 MoE 模型在一台 Mac 上流式跑起来
python·开源·github
悟天特斯1 小时前
安防一体化:从“各管各的“到“全局联动“
java·大数据·网络·人工智能·物联网
l1t1 小时前
DeepSeek总结的AI 智能体应该用哪种语言搜索其记忆:SQL 还是 SPARQL?
数据库·人工智能·sql
MyFreeIT1 小时前
Python Windows Manual
开发语言·python
骑着蜗牛撵大象3271 小时前
手写一个 HTTP 服务器:从 socket 到 epoll,Linux 上到底怎么把请求变成响应
linux·服务器·网络·http·epoll·socket编程·并发模型
wusam1 小时前
基于HCL的python脚本编程(2)
网络·python·智能路由器