一. FastDFS 介绍

  • FastDFS 是一个开源的分布式文件存储系统,主要用于存储图片、视频等大容量或海量小文件。它采用 Tracker + Storage 的架构设计。
  • Tracker 是调度与管理节点,负责管理所有 Storage 的状态信息,包括节点存活情况、分组信息等。Tracker 本身不存储文件,也不参与文件传输,主要作用是为客户端分配合适的 Storage 节点。Tracker 无状态,可部署多个实例,提高系统的可用性。
  • Storage 是实际的存储节点,负责文件的上传、下载和数据保存。Storage 按 Group 进行组织,同一 Group 内的多个 Storage 节点之间会进行数据同步,实现数据冗余和高可用;不同 Group 之间的数据相互独立,用于系统扩展。
  • 客户端在上传或下载文件时,首先向 Tracker 查询可用的 Storage,然后直接与 Storage 进行数据传输,从而实现高性能的分布式文件存储。

在这里插入图片描述

FastDFS 特点:

  • 高性能:采用 Tracker 与 Storage 分离的架构,客户端直接与 Storage 节点进行数据传输,减少中间环节,适合高并发读写场景。
  • 高可用性:通过 Group 机制实现数据冗余,同一组内的多个 Storage 节点之间自动同步数据,单个节点故障不会影响整体服务。
  • 良好的扩展性:支持水平扩展,可通过增加 Storage 组或节点来提升存储容量和访问能力,无需停机。
  • 轻量且易部署:架构简单、依赖少,部署和运维成本低,适合中小规模分布式存储系统。
  • 无中心化存储节点:Tracker 不存储文件,也不参与数据传输,避免性能瓶颈,系统更稳定。
  • 支持多种客户端:提供多语言客户端接口,方便与业务系统集成。
  • 常与 Nginx 结合使用:可通过 FastDFS + Nginx 提供 HTTP 方式的文件访问,适合图片、视频等资源服务场景。

二. FastDFS 安装

# 1. 更新软件源
sudo apt update
# 2. 安装依赖
sudo apt install -y gcc g++ make libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev git
# 3. 安装 libfastcommon
cd /usr/local/src
sudo git clone https://github.com/happyfish100/libfastcommon.git
cd libfastcommon
sudo ./make.sh
sudo ./make.sh install
# 4. 安装 FastDFS
cd /usr/local/src
sudo git clone https://github.com/happyfish100/fastdfs.git
cd fastdfs
sudo ./make.sh
sudo ./make.sh install
# 5. 拷贝配置文件
sudo cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
sudo cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
sudo cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
# 6. 创建数据目录
sudo mkdir -p /data/fastdfs/tracker
sudo mkdir -p /data/fastdfs/storage
# 7. 配置 Tracker
sudo sed -i 's|^disabled=true|disabled=false|' /etc/fdfs/tracker.conf
sudo sed -i 's|^base_path=.*|base_path=/data/fastdfs/tracker|' /etc/fdfs/tracker.conf
# 8. 启动 Tracker 服务
sudo fdfs_trackerd /etc/fdfs/tracker.conf start
# 9. 配置 Storage
sudo sed -i 's|^disabled=true|disabled=false|' /etc/fdfs/storage.conf
sudo sed -i 's|^base_path=.*|base_path=/data/fastdfs/storage|' /etc/fdfs/storage.conf
sudo sed -i 's|^store_path0=.*|store_path0=/data/fastdfs/storage|' /etc/fdfs/storage.conf
sudo sed -i 's|^tracker_server=.*|tracker_server=127.0.0.1:22122|' /etc/fdfs/storage.conf
# 10. 启动 Storage 服务
sudo fdfs_storaged /etc/fdfs/storage.conf start

由于在环境搭建中已经安装了 FastDFS 服务器,并且启动了:

在这里插入图片描述

三. fastdfs SDK 介绍

fastdfs 是 FastDFS 分布式文件系统为 C/C++ 程序提供的官方客户端开发库,主要用于实现文件的上传、下载、删除及元数据查询等功能。该客户端通过与 Tracker 和 Storage 服务器通信,完成文件定位与存储管理,具有高性能、低开销的特点,适合对效率要求较高的业务场景。fastdfs 客户端封装了底层网络通信和协议细节,开发者只需调用相关 API 即可快速集成分布式文件存储能力。它广泛应用于图片服务器、日志存储、音视频文件管理等场景,支持大规模文件并发访问,具备良好的可扩展性和稳定性,是使用 FastDFS 的核心基础组件之一。

四. fastdfs SDK 安装

fastdfs 的项目地址为:https://github.com/happyfish100/fastdfs.git

# 1. 安装 libfastcommon
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd libfastcommon
./make.sh
sudo ./make.sh install
cd ..
# 2. 安装 libserverframe
git clone https://github.com/happyfish100/libserverframe.git --depth 1
cd libserverframe
./make.sh
sudo ./make.sh install
cd ..
# 3. 编译并安装 FastDFS
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs
./make.sh
sudo ./make.sh install
# 4. 复制配置文件(供 Nginx 访问使用)
sudo cp conf/http.conf /etc/fdfs/
sudo cp conf/mime.types /etc/fdfs/

由于在环境搭建中已经安装了 fastdfs,这里不需要再次安装了。

五. fastdfs 类与接口

1. 日志、错误信息、客户端配置

fastdfs 有自己的日志输出模块 (无法替换成为我们封装的 spdlog 日志输出,除非去修改 fastdfs 的源代码,将所有的日志输出操作进行替换),这里介绍 fastdfs 的日志相关操作,主要是为了让 fastdfs 只输出错误日志,避免因为大量的第三方库日志输出影响我们的视线。

Error 用于获取 fastdfs 接口调用失败的错误原因接口。

// 全局日志上下文
typedef struct log_context {
    int log_level; // 日志级别
    int log_fd; // 日志文件描述符
    //.....
};
// 全局日志上下文实例
extern LogContext g_log_context;
// 初始化全局日志上下文
int log_init();
// 忽略SIGPIPE信号
int ignore_signal_pipe();

// 定义一个宏,用于获取错误码对应的错误信息字符串
#define STRERROR(no) (strerror(no) != NULL ? strerror(no) : "Unkown error")

初始化客户端全局配置,默认可以通过配置文件进行初始化,其实也可以将配置信息整到内存中,通过内存数据进行客户端全局配置初始化。

connect_timeout = 5 // 连接超时时间,单位秒
network_timeout = 60 // 网络超时时间,单位秒
tracker_server = 192.168.174.128:22122 // Tracker服务器地址和端口
use_connection_pool = true // 是否使用连接池
connection_pool_max_idle_time = 3600 // 连接池最大空闲时间,单位秒
// 从配置文件初始化客户端配置 
#define fdfs_client_init(filename) fdfs_client_init_ex((&g_tracker_group), filename)
int fdfs_client_init_ex(TrackerServerGroup *pTrackerGroup, const char *conf_filename);

// 根据给定内存数据初始化客户端配置 
#define fdfs_client_init_from_buffer(buffer) fdfs_client_init_from_buffer_ex((&g_tracker_group), buffer)
int fdfs_client_init_from_buffer_ex(TrackerServerGroup *pTrackerGroup, const char *buffer);

// 销毁客户端配置 
#define fdfs_client_destroy() fdfs_client_destroy_ex((&g_tracker_group))
void fdfs_client_destroy_ex(TrackerServerGroup *pTrackerGroup);

2. Tracker、Storage

获取 Tracker 句柄,用于访问 tracker 服务器。

typedef struct {
    int sock; // 套接字描述符
    uint16_t port; // 端口号
    short af; // 地址族
    FCCommunicationType comm_type; // 通信类型
    bool validate_flag; // 验证标志
    char ip_addr[IP_ADDRESS_SIZE]; // IP地址
    void *arg1; // 自定义参数1
    char args[0]; // 自定义参数2
} ConnectionInfo;

// 从连接池获取一个tracker连接
#define tracker_get_connection() tracker_get_connection_ex((&g_tracker_group))
ConnectionInfo *tracker_get_connection_ex(TrackerServerGroup *pTrackerGroup);
// 关闭连接:关闭指定的tracker连接
#define tracker_close_connection(pTrackerServer) tracker_close_connection_ex(pTrackerServer, false)
void tracker_close_connection_ex(ConnectionInfo *conn, const bool bForceClose);

从 Tracker 服务器上获取 Storage 信息,并返回 storage 节点操作句柄。

#define FDFS_GROUP_NAME_MAX_LEN 16

// 查询存储节点的存储路径索引
// pTrackerServer:tracker服务器连接信息
// pStorageServer:存储服务器连接信息
// group_name:文件组名
// store_path_index:存储路径索引
#define tracker_query_storage_store(pTrackerServer, pStorageServer, group_name, store_path_index) \
    tracker_query_storage_store_without_group(pTrackerServer, pStorageServer, group_name, store_path_index)
int tracker_query_storage_store_without_group(ConnectionInfo *pTrackerServer, 
    ConnectionInfo *pStorageServer, char *group_name, int *store_path_index);

3. 文件上传、下载、删除

文件上传相关接口:

// 上传文件到指定的storage节点
// pTrackerServer: tracker 连接信息
// pStorageServer: storage 连接信息,可以为 nullptr
// store_path_index: 获取storage节点时顺便获取的索引, 若没有主动获取节点则可以为0 
// cmd: 上传文件命令,默认值为STORAGE_PROTO_CMD_UPLOAD_FILE
// local_filename: 本地要上传的文件路径名 
// file_ext_name: 文件扩展名,可以为 nullptr
// meta_list: 文件元信息数组 
// meta_count: 元信息元素数量,若没有传递元信息数组则设置为0 
// group_name: 若没有主动获取storage节点,则可以设置为 nullptr
// file_id: 输出参数--是文件上传保存成功后分配的标识ID
int storage_upload_by_filename1_ex(ConnectionInfo *pTrackerServer, \
    ConnectionInfo *pStorageServer, const int store_path_index, \
    const char cmd, const char *local_filename, \
    const char *file_ext_name, const FDFSMetaData *meta_list, \
    const int meta_count, const char *group_name, char *file_id);

// 上传文件内容到指定的storage节点
// pTrackerServer: tracker 连接信息
// pStorageServer: storage 连接信息,可以为 nullptr
// store_path_index: 获取storage节点时顺便获取的索引, 若没有主动获取节点则可以为0 
// cmd: 上传文件命令,默认值为STORAGE_PROTO_CMD_UPLOAD_FILE
// upload_type: 上传文件类型
// file_buff: 文件内容指针
// arg: 上传文件类型为FDFS_UPLOAD_BY_BUFF时,传递的参数指针,其他类型时可以为 nullptr
// file_size: 文件内容大小
// file_ext_name: 文件扩展名,可以为 nullptr
// meta_list: 文件元信息数组 
// meta_count: 元信息元素数量,若没有传递元信息数组则设置为0 
// group_name: 若没有主动获取storage节点,则可以设置为 nullptr
// file_id: 输出参数--是文件上传保存成功后分配的标识ID
int storage_do_upload_file1(ConnectionInfo *pTrackerServer, \
    ConnectionInfo *pStorageServer, const int store_path_index, \
    const char cmd, const int upload_type, \
    const char *file_buff, void *arg, const int64_t file_size, \
    const char *file_ext_name, const FDFSMetaData *meta_list, \
    const int meta_count, const char *group_name, char *file_id);

// 宏定义
#define storage_upload_by_filename1(pTrackerServer, pStorageServer, \
    store_path_index, local_filename, file_ext_name, \
    meta_list, meta_count, group_name, file_id) \
storage_upload_by_filename1_ex(pTrackerServer, pStorageServer, \
    store_path_index, STORAGE_PROTO_CMD_UPLOAD_FILE, \
    local_filename, file_ext_name, meta_list, meta_count, \
    group_name, file_id)
#define storage_upload_by_filebuff1(pTrackerServer, pStorageServer, \
    store_path_index, file_buff, file_size, file_ext_name, \
    meta_list, meta_count, group_name, file_id) \
storage_do_upload_file1(pTrackerServer, pStorageServer, \
    store_path_index, STORAGE_PROTO_CMD_UPLOAD_FILE, \
    FDFS_UPLOAD_BY_BUFF, file_buff, NULL, \
    file_size, file_ext_name, meta_list, meta_count, \
    group_name, file_id)
    
// 文件元信息配置结构 
typedef struct {
    char name[FDFS_MAX_META_NAME_LEN + 1]; // 元信息键
    char value[FDFS_MAX_META_VALUE_LEN + 1]; // 元信息值
} FDFSMetaData;

// 获取文件元信息 
int storage_get_metadata1(ConnectionInfo *pTrackerServer, ConnectionInfo *pStorageServer, 
    const char *file_id, FDFSMetaData **meta_list, int *meta_count);

文件下载相关接口:

// 从Storage下载文件到内存
// pTrackerServer: tracker 连接信息
// pStorageServer: storage 连接信息
// download_type: 下载类型,FDFS_DOWNLOAD_TO_BUFF或FDFS_DOWNLOAD_TO_FILE
// file_id: 文件ID
// file_offset: 文件偏移量
// download_bytes: 下载字节数
// file_buff: 文件缓冲区
// arg: 回调函数参数
// file_size: 文件大小
int storage_do_download_file1_ex(ConnectionInfo *pTrackerServer, ConnectionInfo *pStorageServer, \
    const int download_type, const char *file_id, const int64_t file_offset, const int64_t download_bytes, \
    char **file_buff, void *arg, int64_t *file_size);
    
// 从Storage下载文件到本地文件
// pTrackerServer: tracker 连接信息
// pStorageServer: storage 连接信息
// file_id: 文件ID
// local_filename: 本地文件名
// file_size: 文件大小
int storage_download_file_to_file1(ConnectionInfo *pTrackerServer, ConnectionInfo *pStorageServer, \
    const char *file_id, const char *local_filename, int64_t *file_size);
    
// 宏定义
#define storage_download_file1(pTrackerServer, pStorageServer, file_id, file_buff, file_size) \
    torage_do_download_file1_ex(pTrackerServer, pStorageServer, FDFS_DOWNLOAD_TO_BUFF, file_id, 0, 0, file_buff, NULL, file_size)
#define storage_download_file_to_buff1(pTrackerServer, pStorageServer, file_id, file_buff, file_size) \
    storage_do_download_file1_ex(pTrackerServer, pStorageServer, FDFS_DOWNLOAD_TO_BUFF, file_id, 0, 0, file_buff, NULL, file_size)

文件删除接口:

// 删除Storage文件
// pTrackerServer: tracker 连接信息
// pStorageServer: storage 连接信息
// file_id: 文件ID
int storage_delete_file1(ConnectionInfo *pTrackerServer, ConnectionInfo *pStorageServer, const char *file_id);

六. fastdfs 使用样例

1. 目录结构

example/
|-- fastdfs
|   |-- client.conf
|   |-- download.cc
|   |-- makefile
|   |-- remove.cc
|   |-- upload.cc

2. 项目构建

# makefile
all: upload download remove
upload: upload.cc
	g++ -o upload upload.cc -lfastcommon -lfdfsclient
download: download.cc
	g++ -o download download.cc -lfastcommon -lfdfsclient
remove: remove.cc
	g++ -o remove remove.cc -lfastcommon -lfdfsclient

.PHONY: clean
clean:
	rm -f upload download remove

编译生成可执行文件:

在这里插入图片描述

3. 代码实现

3.1 配置文件

# client.conf
connect_timeout = 5 // 连接超时时间,单位秒
network_timeout = 60 // 网络超时时间,单位秒
tracker_server = 192.168.174.128:22122 // Tracker服务器地址和端口
use_connection_pool = true // 是否使用连接池
connection_pool_max_idle_time = 3600 // 连接池最大空闲时间,单位秒

3.2 文件上传

// upload.cc
#include <iostream>
#include <fastcommon/logger.h>
#include <fastdfs/fdfs_client.h>

int main(int argc, char* argv[])
{   
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " file_path" << std::endl;
        return -1;
    }
    // 1.初始化日志输出模块
    g_log_context.log_level = LOG_ERR;
    log_init();
    // 2.初始化FastDFS客户端全局配置
    int ret = fdfs_client_init("./client.conf");
    if (ret != 0) {
        std::cout << "初始化FastDFS客户端全局配置失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    // 3.获取tracker服务器连接句柄
    ConnectionInfo* tracker_server = tracker_get_connection();
    if (tracker_server == nullptr) {
        std::cout << "获取tracker服务器连接句柄失败" << std::endl;
        return -1;
    }
    // 4.上传文件到FastDFS
    char file_id[256];
    ret = storage_upload_by_filename1(tracker_server, nullptr, 0, argv[1], nullptr, nullptr, 0, nullptr, file_id);
    if (ret != 0) {
        std::cout << "上传文件到FastDFS失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    std::cout << "上传文件到FastDFS成功:" << file_id << std::endl;
    // 5.释放资源:关闭tracker服务器连接句柄和FastDFS客户端全局配置
    tracker_close_connection(tracker_server);
    fdfs_client_destroy();

    return 0;
}

上传 makefile 文件到 FastDFS 服务器中,返回文件 ID:

在这里插入图片描述

3.3 文件下载

// download.cc
#include <iostream>
#include <fastcommon/logger.h>
#include <fastdfs/fdfs_client.h>

int main(int argc, char* argv[])
{   
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " file_id" << std::endl;
        return -1;
    }
    // 1.初始化日志输出模块
    g_log_context.log_level = LOG_ERR;
    log_init();
    // 2.初始化FastDFS客户端全局配置
    int ret = fdfs_client_init("./client.conf");        
    if (ret != 0) {
        std::cout << "初始化FastDFS客户端全局配置失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    // 3.获取tracker服务器连接句柄
    ConnectionInfo* tracker_server = tracker_get_connection();
    if (tracker_server == nullptr) {
        std::cout << "获取tracker服务器连接句柄失败" << std::endl;
        return -1;
    }
    // 4.下载文件到本地
    int64_t file_size;
    ret = storage_download_file_to_file1(tracker_server, nullptr, argv[1], "./makefile.back", &file_size);
    if (ret != 0) {
        std::cout << "下载文件失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    std::cout << "下载文件成功:" << file_size << std::endl;
    // 5.释放资源:关闭tracker服务器连接句柄和FastDFS客户端全局配置
    tracker_close_connection(tracker_server);
    fdfs_client_destroy();

    return 0;
}

根据 FastDFS 服务器中的文件 ID,下载文件到本地:

在这里插入图片描述

3.4 文件删除

// remove.cc
#include <iostream>
#include <fastcommon/logger.h>
#include <fastdfs/fdfs_client.h>

int main(int argc, char* argv[])
{   
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " file_id" << std::endl;
        return -1;
    }
    // 1.初始化日志输出模块
    g_log_context.log_level = LOG_ERR;
    log_init();
    // 2.初始化FastDFS客户端全局配置
    int ret = fdfs_client_init("./client.conf");
    if (ret != 0) {
        std::cout << "初始化FastDFS客户端全局配置失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    // 3.获取tracker服务器连接句柄
    ConnectionInfo* tracker_server = tracker_get_connection();
    if (tracker_server == nullptr) {
        std::cout << "获取tracker服务器连接句柄失败" << std::endl;
        return -1;
    }
    // 4.删除FastDFS文件
    ret = storage_delete_file1(tracker_server, nullptr, argv[1]);
    if (ret != 0) {
        std::cout << "删除FastDFS文件失败:" << STRERROR(ret) << std::endl;
        return -1;
    }
    std::cout << "删除FastDFS文件成功:" << argv[1] << std::endl;
    // 5.释放资源:关闭tracker服务器连接句柄和FastDFS客户端全局配置
    tracker_close_connection(tracker_server);
    fdfs_client_destroy();

    return 0;
}

根据文件 ID,删除FastDFS 服务器中的文件,顺便删除本地下载的 makefile.back 文件:

在这里插入图片描述

七. fastdfs 封装

1. 设计与实现

FDFS 这边封装思想比较简单,根据提供的 API 接口封装一个 FDFS 客户端类,将初始化和请求过程封装起来,向外提供文件上传,下载,删除的接口即可 (上传和下载接口都需要提供两种,基于缓冲区和文件的操作,因为网络通信中很多时候都是针对收到的数据进行操作,完全没必要把数据先存文件再上传)

除此之外,为了便于初始化客户端,因此定义一个客户端配置信息结构体,只需要包含一些关键配置信息即可。

1.1 目录结构

source/
|-- fdfs.cc
|-- fdfs.h

1.2 代码实现

// fdfs.h
#pragma once
#include <iostream>
#include <vector>
#include <optional>
extern "C" {
#include <fastdfs/fdfs_client.h>
#include <fastcommon/logger.h>
}

namespace xzyfdfs {
    // FastDFS客户端全局配置
    struct fdfs_settings {
        std::vector<std::string> tracker_servers; // Tracker服务器地址和端口列表
        int connect_timeout = 30; // 连接超时时间,单位秒
        int network_timeout = 30; // 网络超时时间,单位秒
        bool use_connection_pool = true; // 是否使用连接池:多线程环境下开启,保证上传、下载和删除文件等操作的线程安全
        int connection_pool_max_idle_time = 3600; // 连接池最大空闲时间,单位秒
    };
    // FastDFS客户端类
    class FDFSClient {
    public:
        FDFSClient();
        ~FDFSClient();
        // 初始化FastDFS客户端
        static void init(const fdfs_settings& settings);
        // 销毁FastDFS客户端
        static void destroy();
        // 上传文件到FastDFS
        static std::optional<std::string> upload_from_buff(const std::string& buff);
        // 从本地文件上传文件到FastDFS
        static std::optional<std::string> upload_from_file(const std::string& path);
        // 下载文件到内存缓冲区
        static bool download_to_buff(const std::string& file_id, std::string& buff);
        // 从FastDFS下载文件到本地文件
        static bool download_to_file(const std::string& file_id, const std::string& path);
        // 删除FastDFS文件
        static bool remove(const std::string& file_id);
    };
}
// fdfs.cc
#include <sstream>
#include "log.h"
#include "fdfs.h"

namespace xzyfdfs {
    FDFSClient::FDFSClient(){}
    FDFSClient::~FDFSClient(){}
    // 初始化FastDFS客户端
    void FDFSClient::init(const fdfs_settings& settings) {
        // 1.初始化日志输出模块
        g_log_context.log_level = LOG_ERR;
        log_init();
        // 2.针对settings中的客户端配置信息组成一个字符串
        std::stringstream ss;
        for (auto& tracker : settings.tracker_servers) {
            ss << "tracker_server = " << tracker << "\n";
        }
        ss << "connect_timeout = " << settings.connect_timeout << "\n";
        ss << "network_timeout = " << settings.network_timeout << "\n";
        ss << "use_connection_pool = " << settings.use_connection_pool << "\n";
        ss << "connection_pool_max_idle_time = " << settings.connection_pool_max_idle_time << "\n";
        // 3.初始化FastDFS客户端配置
        int ret = fdfs_client_init_from_buffer(ss.str().c_str());
        if (ret != 0) {
            ERR("初始化客户端配置失败:{}", STRERROR(ret));
            abort(); // 异常退出
        }
    }
    // 销毁FastDFS客户端
    void FDFSClient::destroy() {
        fdfs_client_destroy();
    }
    // 上传文件到FastDFS
    std::optional<std::string> FDFSClient::upload_from_buff(const std::string& buff) {
        // 1.获取tracker服务器连接句柄
        ConnectionInfo* tracker_server = tracker_get_connection();
        if (tracker_server == nullptr) {
            ERR("获取tracker服务器连接句柄失败");
            return std::optional<std::string>();
        }
        // 2.上传文件到FastDFS
        char file_id[256];
        int ret = storage_upload_by_filebuff1(tracker_server, nullptr, 0, buff.c_str(), buff.size(), nullptr, nullptr, 0, nullptr, file_id);
        if (ret != 0) {
            ERR("上传文件到FastDFS失败:{}", STRERROR(ret));
            return std::optional<std::string>();
        }
        // 3.销毁tracker服务器连接句柄
        tracker_close_connection(tracker_server);
        return std::string(file_id);
    }
    // 本地文件上传文件到FastDFS
    std::optional<std::string> FDFSClient::upload_from_file(const std::string& path) {
        // 1.获取tracker服务器连接句柄
        ConnectionInfo* tracker_server = tracker_get_connection();
        if (tracker_server == nullptr) {
            ERR("获取tracker服务器连接句柄失败");
            return std::optional<std::string>();
        }
        // 2.本地文件上传文件到FastDFS
        char file_id[256];
        int ret = storage_upload_by_filename1(tracker_server, nullptr, 0, path.c_str(), nullptr, nullptr, 0, nullptr, file_id);
        if (ret != 0) {
            ERR("本地文件上传文件到FastDFS失败:{}", STRERROR(ret));
            return std::optional<std::string>();
        }
        // 3.销毁tracker服务器连接句柄
        tracker_close_connection(tracker_server);
        return std::string(file_id);
    }
    // 下载文件到内存缓冲区
    bool FDFSClient::download_to_buff(const std::string& file_id, std::string& buff) {
        // 1.获取tracker服务器连接句柄
        ConnectionInfo* tracker_server = tracker_get_connection();
        if (tracker_server == nullptr) {
            ERR("获取tracker服务器连接句柄失败");
            return false;
        }
        // 2.下载文件到内存缓冲区
        int64_t file_size;
        char* file_buff = nullptr;
        int ret = storage_download_file1(tracker_server, nullptr, file_id.c_str(), &file_buff, &file_size);
        if (ret != 0) {
            ERR("下载文件到内存缓冲区失败:{}", STRERROR(ret));
            return false;
        }
        buff.assign(file_buff, file_size); // 复制文件内容到buff
        free(file_buff); // 释放文件内容内存
        // 3.销毁tracker服务器连接句柄
        tracker_close_connection(tracker_server);
        return true;
    }
    // 从FastDFS下载文件到本地文件
    bool FDFSClient::download_to_file(const std::string& file_id, const std::string& path) {
        // 1.获取tracker服务器连接句柄
        ConnectionInfo* tracker_server = tracker_get_connection();
        if (tracker_server == nullptr) {
            ERR("获取tracker服务器连接句柄失败");
            return false;
        }
        // 2.从FastDFS下载文件到本地文件
        int64_t file_size;
        int ret = storage_download_file_to_file1(tracker_server, nullptr, file_id.c_str(), path.c_str(), &file_size);
        if (ret != 0) {
            ERR("从FastDFS下载文件到本地文件失败:{}", STRERROR(ret));
            return false;
        }
        // 3.销毁tracker服务器连接句柄
        tracker_close_connection(tracker_server);
        return true;
    }
    // 删除FastDFS文件
    bool FDFSClient::remove(const std::string& file_id) {
        // 1.获取tracker服务器连接句柄
        ConnectionInfo* tracker_server = tracker_get_connection();
        if (tracker_server == nullptr) {
            ERR("获取tracker服务器连接句柄失败");
            return false;
        }
        // 2.删除FastDFS文件
        int ret = storage_delete_file1(tracker_server, nullptr, file_id.c_str());
        if (ret != 0) {
            ERR("删除FastDFS文件失败:{}", STRERROR(ret));
            return false;
        }
        // 3.销毁tracker服务器连接句柄
        tracker_close_connection(tracker_server);
        return true;
    }
}

2. 使用样例

1.1 目录结构

test/
|-- fastdfs
|   |-- fdfs_test
|   |-- fdfs_test.cc
|   |-- makefile

1.2 项目构建

# makefile
fdfs_test: fdfs_test.cc ../../source/log.cc ../../source/util.cc ../../source/fdfs.cc
	g++ -o $@ $^ -std=c++17 -lfmt -lspdlog -ljsoncpp -lfastcommon -lfdfsclient -lpthread

.PHONY: clean
clean:
	rm -f fdfs_test

编译生成可执行程序:

在这里插入图片描述

1.3 代码实现

// fdfs_test.cc
#include "../../source/log.h"
#include "../../source/util.h"
#include "../../source/fdfs.h"

// 基于文件的上传下载测试
std::string file_test() {
    // 1.将文件上传至FastDFS服务器
    std::optional<std::string> id = xzyfdfs::FDFSClient::upload_from_file("./makefile");
    if (!id.has_value()) {
        ERR("上传文件失败");
        abort();
    }
    DBG("上传文件成功:{}", id.value());
    // 2.从FastDFS服务器下载文件到本地
    bool ret = xzyfdfs::FDFSClient::download_to_file(id.value(), "./makefile.download");
    if (!ret) {
        ERR("下载文件失败");
        abort();
    }
    DBG("下载文件成功:{}", id.value());
    return id.value();
}
// 基于内存的上传下载测试
std::string buff_test() {
    // 1.从文件中读取数据到内存
    std::string body;
    xzyutil::FUTIL::read("./makefile", body);
    // 2.从内存上传至FastDFS服务器
    std::optional<std::string> id = xzyfdfs::FDFSClient::upload_from_buff(body);
    if (!id.has_value()) {
        ERR("上传内存数据失败");
        abort();
    }
    DBG("上传内存数据成功:{}", id.value());
    // 3.从FastDFS服务器下载文件到内存
    std::string buff;
    bool ret = xzyfdfs::FDFSClient::download_to_buff(id.value(), buff);
    if (!ret) {
        ERR("下载内存数据失败");
        abort();
    }
    DBG("下载内存数据成功:{}", id.value());
    // 4.将内存中的数据写入文件
    xzyutil::FUTIL::write("./makefile.download2", buff);
    return id.value();
}
// 多线程上传测试
void multi_thread_test(std::vector<std::string>& arr, std::mutex& mtx) {
    // 1.创建3个线程
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; i++) {
        threads.emplace_back([&](){
            // 2.每个线程上传3个文件
            for (int i = 0; i < 3; i++) {
                std::optional<std::string> id = xzyfdfs::FDFSClient::upload_from_file("./makefile");
                if (!id.has_value()) {
                    ERR("上传文件失败");
                    abort();
                }
                // 3.将上传成功的文件ID添加到数组中
                {
                    std::lock_guard<std::mutex> lock(mtx);
                    arr.push_back(id.value());
                }
                DBG("上传文件成功:{}", id.value());
            }
        });
    }
    // 4.等待所有线程执行完毕
    for (auto& th : threads) {
        th.join();
    }
}
// 删除所有上传的文件测试
void remove_test(std::vector<std::string>& arr) {
    for (auto& id : arr) {
        bool ret = xzyfdfs::FDFSClient::remove(id);
        if (!ret) {
            ERR("文件删除失败:{}", id);
            abort();
        }
        DBG("文件删除成功:{}", id);
    }
}

int main()
{
    // 1.初始化日志模块,配置并初始化FastDFS客户端
    xzylog::log_init();
    xzyfdfs::fdfs_settings settings = {
        .tracker_servers = {"192.168.174.128:22122"}
    };
    xzyfdfs::FDFSClient::init(settings);
    // 2.执行上传、下载、删除测试
    std::string id = file_test();
    std::string id2 = buff_test();
    std::vector<std::string> arr = {id, id2};
    remove_test(arr);
    arr.clear();
    // 3.执行多线程上传测试
    std::mutex mtx;
    multi_thread_test(arr, mtx);
    // 4.删除所有上传的文件
    remove_test(arr);
    // 5.释放FastDFS客户端
    xzyfdfs::FDFSClient::destroy();

    return 0;
}

在这里插入图片描述

通过 md5sum 判断是否为同一个文件:

在这里插入图片描述

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐