1.简介
fineftp-server 是一个轻量级 C++ FTP 服务器库,专为 Windows 和 Unix 系统设计,提供极简 API 用于快速集成 FTP 服务功能。它采用 CMake 构建,仅依赖 asio 库 (已作为子模块集成),无需 Boost 支持。
fineftp-server 是一个轻量级 C++ FTP 服务器库,支持 Windows 和 Unix 系统。其环境准备、编译安装及基础使用方法,重点讲解了如何通过修改源码实现断点续传功能,包括 REST 命令解析、STOR/RETR 偏移应用及边界处理。此外,还对比了与其他开源 FTP 服务器的差异,并分享了 Linux 移植过程中的连接问题排查经验。

fineftp-server 是一个轻量级 C++ FTP 服务器库,专为 Windows 和 Unix 系统设计,提供极简 API 用于快速集成 FTP 服务功能。它采用 CMake 构建,仅依赖 asio 库 (已作为子模块集成),无需 Boost 支持。
核心特性:
适用场景:
方法 1:
# 克隆项目
git clone https://github.com/eclipse-ecal/fineftp-server.git
cd fineftp-server
git submodule update --init --recursive # 初始化 asio 子模块
# 创建构建目录并配置
mkdir build
cd build
# 调试模式,可选 Release, 并且需要指定 asio 的地址
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 的路径即可编译成功。
#include <fineftp/server.h>
#include <thread>
int main() {
// 创建 FTP 服务器 (使用 2121 端口,避开需要 root 权限的 21 端口)
fineftp::FtpServer server(2121);
// 添加匿名用户 (可访问 C:\或/)
server.addUserAnonymous("C:\\", fineftp::Permission::All);
// 添加普通用户
server.addUser("user", "password", "/home/user", fineftp::Permission::Read | fineftp::Permission::Write);
// 启动服务器 (线程池大小为 4)
server.start(4);
// 保持运行
while(true) std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
| 方法 | 功能描述 |
|---|---|
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:所有权限客户端连接:
使用 FileZilla 或其他 FTP 客户端,连接至服务器 IP (或 localhost),端口 2121,使用配置好的用户名和密码 (或匿名方式) 登录即可进行文件操作。
fineftp-server 原生已预留 REST 命令框架,但部分版本存在「偏移量未生效」「文件覆盖而非续传」等问题,需通过源码修改强化处理逻辑。以下基于 fineftp-server v1.1.0 版本(最新稳定版),聚焦 REST 命令解析、STOR/RETR 偏移应用、文件打开模式优化三大核心点,提供完整修改方案。
步骤 1:定位核心文件
需修改 2 个关键文件(fineftp-server 源码根目录 /src 下):
ftp_session.h:会话类头文件(添加偏移量成员);ftp_session.cpp:会话逻辑实现(处理 REST/STOR/RETR 命令)。步骤 2:修改 ftp_session.h(添加偏移量成员)
在 FtpSession 类中添加「续传偏移量」和「偏移量是否有效」标记,用于存储当前连接的续传位置:
// ftp_session.h
#include <cstdint> // 需包含该头文件以支持 uint64_t
class FtpSession {
private:
// 新增以下 2 个成员变量(放在私有成员区域)
uint64_t rest_offset_ = 0; // 续传偏移量(默认 0,即从头传输)
bool rest_offset_valid_ = false; // 偏移量是否有效(避免 REST 后未执行 STOR/RETR)
// 其他原有成员(如 m_server、m_socket 等)...
public:
// 新增重置偏移量的接口(可选,用于后续优化)
void resetRestOffset() { rest_offset_ = 0; rest_offset_valid_ = false; }
// 其他原有接口(如 start、handleCommand 等)...
};
步骤 3:修改 ftp_session.cpp(完善命令处理逻辑)
1) 处理 REST 命令(解析偏移量)
找到 handleCommand 函数中处理 REST 命令的分支(若无则新增),解析客户端传入的偏移量,校验合法性并存储:
// ftp_session.cpp
void FtpSession::handleCommand(const std::string& cmd, const std::string& args) {
// 其他命令处理(如 USER、PASS、PASV 等)...
// 新增/修改 REST 命令处理分支
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");
}
}
// 其他命令处理(如 STOR、RETR 等)...
}
2) 修改 STOR 命令(上传续传:从偏移量写入)
找到 handleStor 函数(处理文件上传),修改文件打开模式,添加偏移量定位逻辑:
// ftp_session.cpp
void FtpSession::handleStor(const std::string& filename) {
// 1. 拼接完整文件路径(原有逻辑保留)
std::string filePath = getAbsolutePath(filename);
if (!checkFileAccess(filePath, Permission::Write)) {
sendResponse(550, "Permission denied");
return;
}
// 2. 优化文件打开模式(关键修改)
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;
}
// 3. 打开文件
std::ofstream file(filePath, openMode);
if (!file.is_open()) {
sendResponse(550, "Failed to open file for writing");
resetRestOffset(); // 打开失败,重置偏移量
return;
}
// 4. 续传偏移量定位(关键修改)
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");
}
// 5. 数据传输(原有逻辑保留,无需修改)
transferDataToFile(file);
file.close();
// 6. 传输完成后重置偏移量(关键:避免影响后续传输)
resetRestOffset();
sendResponse(226, "File upload completed successfully");
}
3) 修改 RETR 命令(下载续传:从偏移量读取)
找到 handleRetr 函数(处理文件下载),添加偏移量定位和文件大小修正逻辑:
// ftp_session.cpp
void FtpSession::handleRetr(const std::string& filename) {
// 1. 拼接完整文件路径(原有逻辑保留)
std::string filePath = getAbsolutePath(filename);
if (!checkFileAccess(filePath, Permission::Read)) {
sendResponse(550, "Permission denied");
return;
}
// 2. 打开文件(只读二进制模式)
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;
}
// 3. 获取文件总大小(原有逻辑保留)
file.seekg(0, std::ios::end);
uint64_t fileSize = static_cast<uint64_t>(file.tellg());
file.seekg(0, std::ios::beg);
// 4. 续传偏移量处理(关键修改)
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)");
}
// 5. 数据传输(原有逻辑保留,无需修改)
transferFileToData(file);
file.close();
// 6. 传输完成后重置偏移量
resetRestOffset();
sendResponse(226, "File download completed successfully");
}
4) 优化偏移量重置逻辑(避免残留影响)
在「传输取消」「命令切换」时重置偏移量,确保后续传输不受之前 REST 命令影响。在 ftp_session.cpp 中添加以下场景的重置:
STOR/RETR 后(已在上述代码中添加 resetRestOffset());ABOR(取消传输)命令时:// 在 handleCommand 中找到 ABOR 命令分支,添加重置
else if (cmd == "ABOR") {
// 原有取消传输逻辑...
resetRestOffset(); // 新增:取消后重置偏移量
sendResponse(226, "Transfer aborted");
}
QUIT(断开连接)命令时:else if (cmd == "QUIT") {
resetRestOffset(); // 新增:断开前重置偏移量
sendResponse(221, "Goodbye");
m_running = false;
}
5)客户端验证步骤
REST 65536(偏移量 64KB)→ 发送 STOR test.bin,服务器会从 1KB 位置写入文件。1. 在 windows 上发送 REST 指令的时候,偏移大小一定要是 64K 的整数倍。 那是因为 fineftp-server 内部读取文件是用的内存映射,函数 MapViewOfFile 的文件偏移量未对齐,偏移量必须是 64KB (0x10000) 的整数倍,这是 Windows 内存映射的基本粒度要求。
错误示例:
// 偏移量 12345 不是 64KB 倍数,会导致 1132 错误
MapViewOfFile(hMap, FILE_MAP_READ, 0, 12345, 0);
修正偏移量对齐问题
// 正确方式:确保偏移量是 64KB 的倍数
#define FILE_OFFSET_ALIGN 0x10000 // 64KB
// 计算正确的偏移量
DWORD dwFileOffset = desiredOffset & ~(FILE_OFFSET_ALIGN - 1);
// 使用正确的偏移量调用 MapViewOfFile
MapViewOfFile(hMap, FILE_MAP_READ, (DWORD)(dwFileOffset >> 32), // 高 32 位
(DWORD)(dwFileOffset & 0xFFFFFFFF), // 低 32 位
dwNumberOfBytesToMap);
断点续传上传文件的时候,服务器是追加文件,当上传到服务器的文件超过 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();
}
}
同样的代码在 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 连接数据,应用层没有读取这个数据。
再想办法,把代码中一些可疑之处修改了一下:
// 若你修改了 fineftp 的 accept 逻辑,必须按这个格式写
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
try {
if (!ec) {
// 处理新连接,创建 FTP 会话
std::make_shared<FtpSession>(std::move(socket_))->start();
}
} catch (...) {
// 捕获所有异常,绝对不能让回调异常退出
}
// 【核心】无论是否出错,必须重新发起 async_accept
start_accept();
});
给 io_context 加 work_guard 保活。
给 fineftp 设置更大的 listen backlog
ftp_server.setListenBacklog(1024);
发现都不行,最后把 fineftp 的整个代码移植到工程中,通过源码集成;启动 ftp 服务,结果 FileZilla 客户端可以连接了,问题解决了,但是还是不知道具体什么原因。
| 特性 | fineftp-server | vsftpd | ProFTPD | Pure-FTPd |
|---|---|---|---|---|
| 架构 | C++ 库 (需集成) | 独立守护进程 | 独立守护进程 | 独立守护进程 |
| 跨平台 | Windows/Unix | Linux/BSD | 全平台 | 全平台 |
| TLS/SSL | ❌ | ✅ | ✅ | ✅ |
| 用户管理 | 简单 API 配置 | 配置文件 / 虚拟用户 | 强大虚拟用户 / 认证后端 | 多种认证方式 |
| 性能 | 轻量级 (单线程 / 多线程可选) | 极高 (专为速度设计) | 高 (可配置) | 中等 |
| 配置复杂度 | 极简 (代码级) | 简单 (配置文件) | 复杂 (类 Apache) | 中等 (命令行 / 配置) |
| 资源占用 | 极低 | 低 (~126KB) | 中等 | 中等 |
| 适用场景 | 嵌入式 / C++ 应用集成 | 生产环境高安全需求 | 复杂企业需求 | 中小型站点 |
| 开源协议 | MIT | GPL | GPL | GPL |
vsftpd:Linux 服务器首选,以 "安全、高效、轻量" 著称,拥有最小代码库减少攻击面,默认启用 chroot 隔离,支持 TLS 加密和虚拟用户。适合高安全要求的生产环境。与 fineftp-server 相比,它是独立守护进程而非库,配置更灵活但复杂度较高。
ProFTPD:提供 Apache 风格配置,支持虚拟主机、LDAP 认证、带宽限制等高级特性,适合需要高度定制化的企业环境。与 fineftp-server 相比,它功能更全面但资源占用更高,配置更复杂。
Pure-FTPd:设计简单易用,提供多种认证方式和配额管理,支持 TLS 加密和虚拟用户。与 fineftp-server 相比,它是独立服务器,提供更完善的管理功能和安全性。
fineftp-server 独特优势:
fineftp-server 是轻量级 FTP 解决方案,特别适合需要在 C++ 应用中快速集成 FTP 服务的场景。它的极简设计和 API 使其成为嵌入式系统、测试环境和内部网络文件共享的理想选择。
使用建议:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online