跳到主要内容FineFTP-Server 轻量级 C++ 跨平台 FTP 服务器指南 | 极客日志C++
FineFTP-Server 轻量级 C++ 跨平台 FTP 服务器指南
综述由AI生成fineftp-server 是一个轻量级 C++ FTP 服务器库,支持 Windows 和 Unix 系统。其环境准备、编译安装及基础使用方法,重点讲解了如何通过修改源码实现断点续传功能,包括 REST 命令解析、STOR/RETR 偏移应用及边界处理。此外,还对比了与其他开源 FTP 服务器的差异,并分享了 Linux 移植过程中的连接问题排查经验。
云间漫步28 浏览 1.简介
fineftp-server 是一个轻量级 C++ FTP 服务器库,专为 Windows 和 Unix 系统设计,提供极简 API 用于快速集成 FTP 服务功能。它采用 CMake 构建,仅依赖 asio 库 (已作为子模块集成),无需 Boost 支持。
核心特性:
- 支持 FTP 被动模式 (现代网络必备)
- 文件操作:上传、下载、创建、删除文件和目录
- 用户认证与匿名访问支持
- 基于用户的独立主目录和权限控制
- UTF-8 支持 (Windows MSVC 版本)
- 跨平台:Windows、Linux、macOS
- 无加密支持:仅适合信任网络环境
适用场景:
- 嵌入式设备文件服务
- 开发测试环境临时 FTP
- 需要快速集成 FTP 功能的 C++ 应用
- 企业内部网络文件共享 (安全要求不高时)
2.安装指南
2.1.环境准备
- CMake (3.10+)
- C++17 兼容编译器 (Windows: VS 2015 + 或 MinGW; Linux: GCC 7.4.0+)
- Git (用于获取源码)
2.2.编译安装步骤
方法 1:
git clone https://github.com/eclipse-ecal/fineftp-server.git
cd fineftp-server
git submodule update --init --recursive
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -Dasio_INCLUDE_DIR=D:/OpenProject/asio-1.30.2/include
cmake --build . --config Debug
可选参数:
-DFINEFTP_SERVER_BUILD_SAMPLES=OFF:不编译示例
-DFINEFTP_SERVER_USE_BUILTIN_ASIO=OFF:使用系统 asio 而非内置版本
如果 asio 库拉取不下来,可以从官方地址获取:https://think-async.com/Asio/Download.html
方法 2:
直接用 VS2022 打开文件夹,进入 VS2022 配置的时候会提示找不到 asio,再配置一下 asio 的路径即可编译成功。
3.使用方法
3.1.基础使用示例
#include <fineftp/server.h>
#include
{
;
server.(, fineftp::Permission::All);
server.(, , , fineftp::Permission::Read | fineftp::Permission::Write);
server.();
() std::this_thread::(std::chrono::());
;
}
<thread>
int main()
fineftp::FtpServer server(2121)
addUserAnonymous
"C:\\"
addUser
"user"
"password"
"/home/user"
start
4
while
true
sleep_for
seconds
1
return
0
3.2.核心 API 概览
| 方法 | 功能描述 |
|---|
FtpServer(int port) | 创建服务器实例,指定监听端口 |
addUserAnonymous(const std::string& homeDir, Permission perm) | 添加匿名用户,指定主目录和权限 |
addUser(const std::string& username, const std::string& password, const std::string& homeDir, Permission perm) | 添加普通用户,指定用户名、密码、主目录和权限 |
start(int threadPoolSize) | 启动服务器,指定线程池大小 |
stop() | 停止服务器 |
Permission::Read:读取文件 / 目录
Permission::Write:写入 / 修改文件
Permission::Delete:删除文件 / 目录
Permission::All:所有权限
3.3.连接测试
使用 FileZilla 或其他 FTP 客户端,连接至服务器 IP (或 localhost),端口 2121,使用配置好的用户名和密码 (或匿名方式) 登录即可进行文件操作。
4.实现完善断点续传功能
fineftp-server 原生已预留 REST 命令框架,但部分版本存在「偏移量未生效」「文件覆盖而非续传」等问题,需通过源码修改强化处理逻辑。以下基于 fineftp-server v1.1.0 版本(最新稳定版),聚焦 REST 命令解析、STOR/RETR 偏移应用、文件打开模式优化三大核心点,提供完整修改方案。
4.1.核心修改思路
- 会话级偏移存储:为每个 FTP 连接(FtpSession)添加续传偏移量变量,仅对当前连接有效;
- REST 命令完善:解析偏移量参数,校验合法性,返回标准响应;
- STOR 命令适配:续传时以「追加 + 定位」模式打开文件,而非覆盖;
- RETR 命令适配:续传时从偏移量开始读取文件,更新响应头中的文件大小;
- 边界处理:偏移量超文件大小、文件不存在等异常场景返回标准 FTP 错误码。
4.2.具体源码修改步骤
需修改 2 个关键文件(fineftp-server 源码根目录 /src 下):
ftp_session.h:会话类头文件(添加偏移量成员);
ftp_session.cpp:会话逻辑实现(处理 REST/STOR/RETR 命令)。
步骤 2:修改 ftp_session.h(添加偏移量成员)
在 FtpSession 类中添加「续传偏移量」和「偏移量是否有效」标记,用于存储当前连接的续传位置:
#include <cstdint>
class FtpSession {
private:
uint64_t rest_offset_ = 0;
bool rest_offset_valid_ = false;
public:
void resetRestOffset() { rest_offset_ = 0; rest_offset_valid_ = false; }
};
步骤 3:修改 ftp_session.cpp(完善命令处理逻辑)
找到 handleCommand 函数中处理 REST 命令的分支(若无则新增),解析客户端传入的偏移量,校验合法性并存储:
void FtpSession::handleCommand(const std::string& cmd, const std::string& args) {
else if (cmd == "REST") {
if (args.empty()) {
sendResponse(501, "REST command requires an offset parameter");
return;
}
try {
uint64_t offset = std::stoull(args);
rest_offset_ = offset;
rest_offset_valid_ = true;
sendResponse(350, "Restart position accepted (" + std::to_string(offset) + ")");
} catch (const std::invalid_argument&) {
sendResponse(501, "Invalid offset value (must be a non-negative integer)");
} catch (const std::out_of_range&) {
sendResponse(501, "Offset value too large");
}
}
}
2) 修改 STOR 命令(上传续传:从偏移量写入)
找到 handleStor 函数(处理文件上传),修改文件打开模式,添加偏移量定位逻辑:
void FtpSession::handleStor(const std::string& filename) {
std::string filePath = getAbsolutePath(filename);
if (!checkFileAccess(filePath, Permission::Write)) {
sendResponse(550, "Permission denied");
return;
}
std::ios_base::openmode openMode = std::ios::binary | std::ios::out;
if (rest_offset_valid_) {
openMode |= std::ios::in | std::ios::app;
} else {
openMode |= std::ios::trunc;
}
std::ofstream file(filePath, openMode);
if (!file.is_open()) {
sendResponse(550, "Failed to open file for writing");
resetRestOffset();
return;
}
if (rest_offset_valid_) {
file.seekp(rest_offset_, std::ios::beg);
if (file.tellp() != static_cast<std::streamoff>(rest_offset_)) {
sendResponse(550, "Failed to seek to restart position");
file.close();
resetRestOffset();
return;
}
sendResponse(150, "Opening data connection for file upload (restart at " + std::to_string(rest_offset_) + ")");
} else {
sendResponse(150, "Opening data connection for file upload");
}
transferDataToFile(file);
file.close();
resetRestOffset();
sendResponse(226, "File upload completed successfully");
}
3) 修改 RETR 命令(下载续传:从偏移量读取)
找到 handleRetr 函数(处理文件下载),添加偏移量定位和文件大小修正逻辑:
void FtpSession::handleRetr(const std::string& filename) {
std::string filePath = getAbsolutePath(filename);
if (!checkFileAccess(filePath, Permission::Read)) {
sendResponse(550, "Permission denied");
return;
}
std::ifstream file(filePath, std::ios::binary | std::ios::in);
if (!file.is_open()) {
sendResponse(550, "File not found or cannot be opened");
resetRestOffset();
return;
}
file.seekg(0, std::ios::end);
uint64_t fileSize = static_cast<uint64_t>(file.tellg());
file.seekg(0, std::ios::beg);
if (rest_offset_valid_) {
if (rest_offset_ > fileSize) {
sendResponse(550, "Restart offset exceeds file size (" + std::to_string(fileSize) + ")");
file.close();
resetRestOffset();
return;
}
file.seekg(rest_offset_, std::ios::beg);
uint64_t remainingSize = fileSize - rest_offset_;
sendResponse(150, "Opening data connection for file download (restart at " + std::to_string(rest_offset_) + ", remaining " + std::to_string(remainingSize) + " bytes)");
} else {
sendResponse(150, "Opening data connection for file download (size " + std::to_string(fileSize) + " bytes)");
}
transferFileToData(file);
file.close();
resetRestOffset();
sendResponse(226, "File download completed successfully");
}
在「传输取消」「命令切换」时重置偏移量,确保后续传输不受之前 REST 命令影响。在 ftp_session.cpp 中添加以下场景的重置:
- 每次执行
STOR/RETR 后(已在上述代码中添加 resetRestOffset());
- 执行
ABOR(取消传输)命令时:
else if (cmd == "ABOR") {
resetRestOffset();
sendResponse(226, "Transfer aborted");
}
else if (cmd == "QUIT") {
resetRestOffset();
sendResponse(221, "Goodbye");
m_running = false;
}
- 使用 FileZilla 客户端:连接服务器 → 上传大文件 → 中途暂停 / 断开 → 右键「继续上传」,观察文件从断点位置续传,无覆盖;
- 使用自定义客户端:发送
REST 65536(偏移量 64KB)→ 发送 STOR test.bin,服务器会从 1KB 位置写入文件。
4.3.关键注意事项
1. 在 windows 上发送 REST 指令的时候,偏移大小一定要是 64K 的整数倍。
那是因为 fineftp-server 内部读取文件是用的内存映射,函数 MapViewOfFile 的文件偏移量未对齐,偏移量必须是 64KB (0x10000) 的整数倍,这是 Windows 内存映射的基本粒度要求。
MapViewOfFile(hMap, FILE_MAP_READ, 0, 12345, 0);
#define FILE_OFFSET_ALIGN 0x10000
DWORD dwFileOffset = desiredOffset & ~(FILE_OFFSET_ALIGN - 1);
MapViewOfFile(hMap, FILE_MAP_READ, (DWORD)(dwFileOffset >> 32),
(DWORD)(dwFileOffset & 0xFFFFFFFF),
dwNumberOfBytesToMap);
4.4.系统 bug
断点续传上传文件的时候,服务器是追加文件,当上传到服务器的文件超过 4G 字节大小后,客户端再次上传文件就会报错,原因在下面地方:
if (INVALID_HANDLE_VALUE != handle_ && (mode & std::ios::app) == std::ios::app) {
LARGE_INTEGER offset = { 0, 0};
offset.LowPart = ::SetFilePointer(handle_, 0, &offset.HighPart, FILE_END);
if ((INVALID_SET_FILE_POINTER == offset.LowPart) && (GetLastError() != NO_ERROR)) {
close();
}
}
4.5.Linux 移植经验
同样的代码在 Windows 编译链接,运行没有问题;但是移植到 ubuntu 下面,ftp 服务开启正常,但是用 FileZilla 客户端连接不上,在 ubuntu 上面用 ss 命令查看端口占用情况,看到 tcp 端口正常监听。
用 telnet 命令测试 tcp 端口是否可连,结果如下图:
说明 ftp 的端口是打开了的,通过 wireshark 抓包,tcp 三次握手,可以正常建立 tcp 连接,那为什么进入不了 ftp 的相关逻辑流程,即进入不了 asio::ip::tcp::acceptor 的函数 async_accept 设置的异步回调函数中。
于是再用 strace 跟踪进程的系统调用,直接看到内核层面发生了什么:
sudo strace -f -p 你的进程 PID -e trace=accept,read,write
根本没有打印出 accept,read,write 相关的信息,说明进程根本没有这几个 accept,read,write 的函数调用。最上面的图中 Recv-Q 为 1 也可以证明这点,Recv-Q 为 1 意思是内核收到了 tcp 连接数据,应用层没有读取这个数据。
- 给 async_accept 回调加全量异常捕获,确保就算业务代码抛异常,也能重新发起 async_accept,保证循环不中断:
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
try {
if (!ec) {
std::make_shared<FtpSession>(std::move(socket_))->start();
}
} catch (...) {
}
start_accept();
});
-
给 io_context 加 work_guard 保活。
-
给 fineftp 设置更大的 listen backlog
ftp_server.setListenBacklog(1024);
发现都不行,最后把 fineftp 的整个代码移植到工程中,通过源码集成;启动 ftp 服务,结果 FileZilla 客户端可以连接了,问题解决了,但是还是不知道具体什么原因。
5.与其他开源 FTP 服务器对比
5.1.功能特性对比表
| 特性 | fineftp-server | vsftpd | ProFTPD | Pure-FTPd |
|---|
| 架构 | C++ 库 (需集成) | 独立守护进程 | 独立守护进程 | 独立守护进程 |
| 跨平台 | Windows/Unix | Linux/BSD | 全平台 | 全平台 |
| TLS/SSL | ❌ | ✅ | ✅ | ✅ |
| 用户管理 | 简单 API 配置 | 配置文件 / 虚拟用户 | 强大虚拟用户 / 认证后端 | 多种认证方式 |
| 性能 | 轻量级 (单线程 / 多线程可选) | 极高 (专为速度设计) | 高 (可配置) | 中等 |
| 配置复杂度 | 极简 (代码级) | 简单 (配置文件) | 复杂 (类 Apache) | 中等 (命令行 / 配置) |
| 资源占用 | 极低 | 低 (~126KB) | 中等 | 中等 |
| 适用场景 | 嵌入式 / C++ 应用集成 | 生产环境高安全需求 | 复杂企业需求 | 中小型站点 |
| 开源协议 | MIT | GPL | GPL | GPL |
5.2.详细对比分析
vsftpd:Linux 服务器首选,以 "安全、高效、轻量" 著称,拥有最小代码库减少攻击面,默认启用 chroot 隔离,支持 TLS 加密和虚拟用户。适合高安全要求的生产环境。与 fineftp-server 相比,它是独立守护进程而非库,配置更灵活但复杂度较高。
ProFTPD:提供 Apache 风格配置,支持虚拟主机、LDAP 认证、带宽限制等高级特性,适合需要高度定制化的企业环境。与 fineftp-server 相比,它功能更全面但资源占用更高,配置更复杂。
Pure-FTPd:设计简单易用,提供多种认证方式和配额管理,支持 TLS 加密和虚拟用户。与 fineftp-server 相比,它是独立服务器,提供更完善的管理功能和安全性。
- 嵌入式集成:以库形式提供,可无缝融入 C++ 项目,无需单独运行进程
- 极简 API:几行代码即可完成 FTP 服务集成,无需学习复杂配置语法
- 零依赖(除 asio):减少外部依赖,简化部署
- 代码级控制:所有配置通过 API 完成,便于动态调整服务行为
6.总结与建议
fineftp-server 是轻量级 FTP 解决方案,特别适合需要在 C++ 应用中快速集成 FTP 服务的场景。它的极简设计和 API 使其成为嵌入式系统、测试环境和内部网络文件共享的理想选择。
- 仅在信任网络环境中使用 (无加密)
- 如需安全传输,考虑在应用层添加 TLS/SSL 包装
- 对性能要求高的场景,可考虑 vsftpd 等专门优化的独立服务器
- 对需要复杂权限管理的企业环境,建议使用 ProFTPD 或 Pure-FTPd
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online