C++文件服务器项目—FastDFS—1(一)

简介: C++文件服务器项目—FastDFS—1(一)


前言

   本项目使用c++实现一个文件服务器,核心功能是上传与下载。所以该项目可以作为网盘,也可以作为图床来用。

  本文的核心重点是介绍FastDFS概念构成配置文件启动上传下载的实现。后续将逐步介绍nginx,mysql,redis,fastcgi等内容。

  本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

1. 项目架构

  1. 客户端-网络架构
  • b/s:浏览器使用http协议
  • c/s:客户端可以随意选择协议
  1. 反向代理服务器
  • 客户端并不能直接访问web服务器, 直接访问到的是反向代理服务器
  • 客户端静请求发送给反向代理服务器, 反向代理将客户端请求转发给web服务器
  1. 服务端
  • Nginx
  • 可以处理静态请求:html,jpg
  • 无法处理动态请求
  • 服务器部署为集群后, 每台服务器上部署的内容必须相同
  • fastCGI
  • 帮助服务器Nginx处理动态请求
  1. 关系型数据库mysql
  • 存储文件的属性信息
  • 存储用户的属性信息
  1. 非关系型数据库redis
  • 提高程序效率,减轻mysql压力
  • 存储是服务器经常要从关系型数据中读取的数据
  1. FastDFS - 分布式文件系统
  • 存储用户上传的文件
  • 用户下载存储的文件

2. 分布式文件系统

2.1 传统文件系统

   传统的文件系统可以被挂载和卸载,格式:ntfs / fat32 / ext3 / ext4

   一台主机的磁盘插槽不可能是无限的,当磁盘都满了之后想要扩充,而磁盘插槽都被插满了,那么这个时候就需要使用分布式文件系统了。

2.2 分布式文件系统

   完整的文件系统, 不在同一台主机上,而是在很多台主机上,多个分散的文件系统组合在一起,形成了一个完整的文件系统。

分布式文件系统:

  1. 需要有网络
  2. 多台主机(不需要在同一地点)
  3. 需要管理者,管理所有的存储节点
  4. 编写应用层的管理程序(Ceph,TFS,FastDFS,MogileFS,MooseFS,GlusterFS)

3. FastDFS介绍

3.1 fdfs概述

  • 是用c语言编写的一款开源的分布式文件系统。
  • 余庆 - 淘宝的架构师
  • 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,注重高可用、高性能等指标
  • 冗余备份: 纵向扩容
  • 线性扩容: 横向扩容
  • 可以很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
  • 图床
  • 网盘

3.2 fdfs框架中的三个角色

  • 追踪器 ( tracker ) - 管理者 - 守护进程
  • 管理存储节点,可以有多个
  • 存储节点(storage )- 守护进程
  • 存储节点是有多个的
  • 客户端(client) - 不是守护进程, 这是程序猿编写的程序
  • 文件上传
  • 文件下载

3.3 fdfs三个角色之间的关系

上传

下载

  1. 追踪器 (tracker)
  • 最先启动追踪器(存储节点和客户端都需要主动连接追踪器)
  1. 存储节点(storage )
  • 第二个启动的角色
  • 存储节点启动之后, 会单独开一个线程
  • 汇报当前存储节点的容量, 和剩余容量
  • 汇报数据的同步情况
  • 汇报数据被下载的次数
  1. 客户端
  • 最后启动
  • 上传
  • 连接追踪器, 询问存储节点的信息
  • 我要上传1G的文件, 询问哪个存储节点有足够的容量
  • 追踪器查询, 得到结果
  • 追踪器将查到的存储节点的IP+端口发送给客户端
  • 通过得到IP和端口连接存储节点
  • 将文件内容发送给存储节点
  • 下载
  • 连接追踪器, 询问存储节点的信息
  • 问一下, 要下载的文件在哪一个存储节点
  • 追踪器查询, 得到结果
  • 追踪器将查到的存储节点的IP+端口发送给客户端
  • 通过得到IP和端口连接存储节点
  • 下载文件

3.4 fdfs集群

  1. 追踪器集群
  • 为什么需要追踪器集群?
  • 避免单点故障
  • 多个tracker如何工作?
  • 轮询RR
  • 如何实现集群
  • 通过修改配置文件可实现


  1. 存储节点集群
  • fastDFS如何管理存储节点?
  • 通过分组的方式完成的(Group)
  • 集群方式(两种扩容方式)
  • 横向扩容 - 增加容量
  • 纵向扩容 - 数据备份
  • 如何实现集群
  • 通过修改配置文件可实现

横向扩容 - 增加容量:

假设当前有两个组: group1, group2
需要添加一个新的分组 -> group3
新主机属于group3
添加一台新的主机 -> 容量增加了
不同组的主机之间不需要通信

纵向扩容 - 数据备份:

假设当前有两个组: group1, group2
将新的主机放到现有的组中
每个组的主机数量从1 -> N
- 这n台主机的关系就是相互备份的关系
- 同一个组中的主机需要通信
- 每个组的容量 等于 组内容量最小的主机容量(比如Group1的容量是500G)

4. FastDFS的安装

4.1 源码安装详解

安装流程:

  1. 以下文件, 里边有安装步骤
  • readme
  • readme.md
  • INSTALL
  1. 找 可执行文件configure
  • 执行这个可执行文件
  • 检测安装环境
  • 生成 makefile
  1. 执行make命令
  • 编译源代码
  • 生成动态库或静态库
  • 生成可执行程序
  1. 安装make install(需要管理员权限)
  • 将第三步生成的动态库/动态库/可执行程序拷贝到对应的系统目录

4.2 安装源码

  1. 安装 libfastcommon(fastdfs的基础库包)
git clone https://github.com/happyfish100/libfastcommon.git
cd libfastcommon
./make.sh
./make.sh install
  1. 安装 FastDFS
git clone https://gitee.com/fastdfs100/fastdfs.git
cd fastdfs
./make.sh
./make.sh install

4.3 make install做了哪些事

来看看make install都做了哪些事情

4.4 测试

测试

fdfs_test
ls /usr/bin/fdfs_*
root@wxf:/# fdfs_test
This is FastDFS client test program v6.08
Copyright (C) 2008, Happy Fish / YuQing
FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.fastken.com/ 
for more detail.
Usage: fdfs_test <config_file> <operation>
  operation: upload, download, getmeta, setmeta, delete and query_servers
#fastDFS安装的所有的可执行程序: 
root@wxf:/# ls /usr/bin/fdfs_*
/usr/bin/fdfs_appender_test   /usr/bin/fdfs_download_file        /usr/bin/fdfs_test
/usr/bin/fdfs_appender_test1  /usr/bin/fdfs_file_info            /usr/bin/fdfs_test1
/usr/bin/fdfs_append_file     /usr/bin/fdfs_monitor              /usr/bin/fdfs_trackerd
/usr/bin/fdfs_crc32           /usr/bin/fdfs_regenerate_filename  /usr/bin/fdfs_upload_appender
/usr/bin/fdfs_delete_file     /usr/bin/fdfs_storaged             /usr/bin/fdfs_upload_file

  为什么在别的目录输入fdfs_test 也能执行?因为make install的时候,已经将程序拷贝到/usr/bin目录下了。

root@wxf:/temp/fastdfs# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

5. fdfs的配置文件

  在上面已经分析了fdfs的配置文件放在/etc/fdfs/目录下。

root@wxf:/etc/fdfs# ls
client.conf  storage.conf  storage_ids.conf  tracker.conf

5.1 tracker配置文件

  不同的tracker主机需要各自修改自己的配置文件

# 下列是需要修改的地方
# 将追踪器和部署的主机的IP地址进程绑定, 也可以不指定
# 如果不指定, 会自动绑定当前主机IP, 如果是云服务器建议不要写
# 这里的ip和port是提供给storage,client建立连接用的
bind_addr=192.168.109.100
# 追踪器监听的端口
port=22122
# 追踪器存储日志信息的目录, xxx.pid文件, 必须是一个存在的目录
base_path=/fastdfs/tracker

   这里因为fastdfs由client,storage,tracker构成,所以可以创建一个fastdfs目录,里面再分三个子目录,由一个大目录fastdfs进行统一管理。

5.2 storage配置文件

  不同的storage主机需要各自修改自己的配置文件

# 下列是需要修改的地方
# 当前存储节点对应的主机属于哪一个组
group_name=group1
# 当前存储节点和所应该的主机进行IP地址的绑定, 如果不写, 有fastdfs自动绑定
# 这里的ip和port是给client建立连接用的
bind_addr=
# 存储节点绑定的端口
port=23000
# 存储节点写log日志的路径
base_path=/fastdfs/storage
# 存储节点提供的存储文件的路径个数
store_path_count=2
# 具体的存储路径 【M00 M01映射的路径】
store_path0=/fastdfs/storage0
store_path1=/fastdfs/storage1
# 追踪器的地址信息
tracker_server=192.168.109.100:22122 
tracker_server=192.168.109.101:22122

   上面配置两个存储目录和两个追踪器地址只是演示怎么配置而已,后文用的配置如下

# 存储节点写log日志的路径
base_path=/fastdfs/storage
# 存储节点提供的存储文件的路径个数
store_path_count=1
# 具体的存储路径 【M00 M01映射的路径】
store_path0=/fastdfs/storage
# 追踪器的地址信息
tracker_server=192.168.109.100:22122

5.3 client配置文件

# 下列是需要修改的地方
# 客户端写log日志的目录
# 该路径必须存在
# 当前的用户对于该路径中的文件有读写权限
base_path=/fastdfs/client
# 要连接的追踪器的地址信息
tracker_server=192.168.109.100:22122 
tracker_server=192.168.109.101:22122

5.4 集群的配置

  假设现在有6台主机,2台client,2台tracker,2台storage。

  第一步:6台主机全部安装FastDFS

  第二步:修改各自对应的配置文件

6. fdfs的启动

所有的启动程序都在/usr/bin/目录下

root@wxf:/# ls /usr/bin/fdfs_*
/usr/bin/fdfs_appender_test   /usr/bin/fdfs_download_file        /usr/bin/fdfs_test
/usr/bin/fdfs_appender_test1  /usr/bin/fdfs_file_info            /usr/bin/fdfs_test1
/usr/bin/fdfs_append_file     /usr/bin/fdfs_monitor              /usr/bin/fdfs_trackerd
/usr/bin/fdfs_crc32           /usr/bin/fdfs_regenerate_filename  /usr/bin/fdfs_upload_appender
/usr/bin/fdfs_delete_file     /usr/bin/fdfs_storaged             /usr/bin/fdfs_upload_file

   其实下面命令中不指定/usr/bin也可以,我这里为了演示清楚目录,全部补全了。

6.1 第一个启动追踪器 - 守护进程

# 使用的方式如下
# <程序> <配置文件> <行为>
# 启动
#fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) 
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
# 关闭
#fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) stop
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop
# 重启
#fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) restart
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart

6.2 第二个启动存储节点 - 守护进程

# 使用的方式如下
# <程序> <配置文件> <行为>
# 启动
#fdfs_storaged 存储节点的配置文件(/etc/fdfs/stroga.conf)
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
# 关闭
#fdfs_storaged 存储节点的配置文件(/etc/fdfs/stroga.conf) stop
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf stop
# 重启
#fdfs_storaged 存储节点的配置文件(/etc/fdfs/stroga.conf) restart
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart

6.3 第三个启动客户端 - 普通进程

# 使用的方式如下
# <程序> <配置文件> <行为>
# 上传
#fdfs_upload_file 客户端的配置文件(/etc/fdfs/client.conf) 要上传的文件(/etc/fdfs/client.conf)
# 得到的结果字符串: group1/M00/00/00/wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
root@wxf:/# /usr/bin/fdfs_upload_file /etc/fdfs/client.conf /etc/fdfs/client.conf 
group1/M00/00/00/wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
# 下载
#fdfs_download_file 客户端的配置文件(/etc/fdfs/client.conf) 上传成功之后得到的字符串(fileID)
root@wxf:/# /usr/bin/fdfs_download_file /etc/fdfs/client.conf group1/M00/00/00/wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
root@wxf:/# ls
wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
# 发现原来的文件名被修改了,所以需要把原来的文件名和保证之后的文件名做一个映射保存在数据库中

6.4 FastDFS的状态检测

  1. 实现使用工具查看一下是否启动成功
#查看端口是否被占用:
netstat -apn | grep 80
#查看占用某个端口的进程:
sudo lsof -i:80
#关键字搜索某个进程
ps -aux | grep fdfs
lsof -i:22122
lsof -i:23000
ps aux|grep fdfs_

  1. 使用fdfs_monitor检测fastDFS状态
fdfs_monitor /etc/fdfs/client.conf

   可以看到fdfs_monitor 后面跟的是client的配置信息,所以可以把它当作一个客户端,启动时先连接追踪器,向追踪器询问storage的信息

   STORAGE SERVER的状态通常有七种,正常状态必须是ACTIVE

# FDFS_STORAGE_STATUS:INIT      :初始化,尚未得到同步已有数据的源服务器
# FDFS_STORAGE_STATUS:WAIT_SYNC :等待同步,已得到同步已有数据的源服务器
# FDFS_STORAGE_STATUS:SYNCING   :同步中
# FDFS_STORAGE_STATUS:DELETED   :已删除,该服务器从本组中摘除
# FDFS_STORAGE_STATUS:OFFLINE   :离线
# FDFS_STORAGE_STATUS:ONLINE    :在线,尚不能提供服务
# FDFS_STORAGE_STATUS:ACTIVE    :在线,可以提供服务
# 从集群中删除
fdfs_monitor /etc/fdfs/client.conf delete group1 192.168.109.100
# 删除数据文件夹
rm -rf /fastdfs/storage/data
# 重启节点
fdfs_storaged /etc/fdfs/storage.conf restart

7. file_id的构成

  • group1
  • 文件上传到了存储节点的哪一个组
  • 如果有多个组这个组名可变的


  • M00 - 虚拟目录
  • 和存储节点的配置项有映射
  • store_path0=/home/yuqing/fastdfs/data -> M00
    store_path1=/home/yuqing/fastdfs1/data -> M01
      
  • 00/00
  • 实际的路径
  • 可变的


  • wKhS_VlrEfOAdIZyAAAJTOwCGr43848.md
  • 文件名包含的信息
  • 采用Base64编码
  • 包含的字段包括
  • 源storage server Ip 地址
  • 文件创建时间
  • 文件大小
  • 文件CRC32效验码(循环冗余校验)
  • 随机数
wKhS_VlrEfOAdIZyAAAJTOwCGr43848
| 4bytes | 4bytes | 8bytes |4bytes | 2bytes |
| ip | timestamp | file_size |crc32 | 校验值 |

8. 上传下载的代码实现

8.1 使用多进程方式-思路

   我们上面已经用过fdfs_upload_file了,这是fdfs作者提供好的可执行程序,所以用多进程的方式是一种比较投机的方式。将fdfs_upload_file的输出重定位到管道pipe,由父进程读取。

   exec函数协议族函数(exec族函数用一个新的进程映像替换当前进程映像):execl、execlp。

  子进程 -> 执行execlp("fdfs_upload_file" , "xx", arg, NULL)。此时会有结果输出到标准输出终端里面,我们想让它输出到终端,使用dup2(old标准输出, new管道的写端)。将file_id写入pipe中,最终由父进程进行读取。pipe在子进程创建之前创建即可。父进程记得还要wait(),释放子进程的资源。

  • 操作步骤
  1. 创建管道 - pipe
  2. 创建子进程
  3. 子进程干什么?
  • 写管道, 关闭读端
  • 将标准输出 -> 管道的写端
  • 重定向
  • 执行execl命令, 调用另外的进程fdfs_upload_file
  • 子进程退出
  1. 父进程?
  • 读管道, 关闭写端
  • 释放子进程资源 - pcb
  • wait()/ waitpid()

8.2 使用fastDFS API实现-思路

  作者并没有提供api与文档出来,但是我们可以通过fdfs_upload_file.c,看看源码是怎么做的。

   看我写的//TODO 注释即可。那么我们的思路就很简单,把这个main函数改成一个接口即可。

/**
* Copyright (C) 2008 Happy Fish / YuQing
*
* FastDFS may be copied only under the terms of the GNU General
* Public License V3, which may be found in the FastDFS source kit.
* Please visit the FastDFS Home Page http://www.fastken.com/ for more detail.
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "fdfs_client.h"
#include "fastcommon/logger.h"
static void usage(char *argv[]) {
    printf("Usage: %s <config_file> <local_filename> " \
        "[storage_ip:port] [store_path_index]\n", argv[0]);
}
int main(int argc, char *argv[]) {
    char *conf_filename;
    char *local_filename;
    char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
    ConnectionInfo *pTrackerServer;
    int result;
    int store_path_index;
    ConnectionInfo storageServer;
    char file_id[128];
    if (argc < 3) {
        usage(argv);
        return 1;
    }
    log_init();
    g_log_context.log_level = LOG_ERR;
    ignore_signal_pipe();
    conf_filename = argv[1];
    //TODO fdfs_client_init解析传入的配置文件
    if ((result = fdfs_client_init(conf_filename)) != 0) {
        return result;
    }
    //TODO tracker_get_connection在上面client配置文件里面
    //TODO 有追踪器的ip:port,所以这里就是连接追踪器,初始化pTrackerServer指针
    pTrackerServer = tracker_get_connection();
    if (pTrackerServer == NULL) {
        fdfs_client_destroy();
        return errno != 0 ? errno : ECONNREFUSED;
    }
    local_filename = argv[2];
    *group_name = '\0';
    //我们都是传3个参数,这里>=4不用看了
    if (argc >= 4) {
        const char *pPort;
        const char *pIpAndPort;
        pIpAndPort = argv[3];
        pPort = strchr(pIpAndPort, ':');
        if (pPort == NULL) {
            fdfs_client_destroy();
            fprintf(stderr, "invalid storage ip address and " \
                "port: %s\n", pIpAndPort);
            usage(argv);
            return 1;
        }
        storageServer.sock = -1;
        snprintf(storageServer.ip_addr, sizeof(storageServer.ip_addr), \
             "%.*s", (int) (pPort - pIpAndPort), pIpAndPort);
        storageServer.port = atoi(pPort + 1);
        if (argc >= 5) {
            store_path_index = atoi(argv[4]);
        }
        else {
            store_path_index = -1;
        }
    }
        //TODO tracker_query_storage_store追踪器 查询 存储节点 的 存储
        //TODO storageServer是传入参数,对storageServer赋值
        //TODO group_name是一个char数组,对group_name赋值
        //TODO store_path_index是传入参数,对store_path_index赋值
    else if ((result = tracker_query_storage_store(pTrackerServer, \
                    &storageServer, group_name, &store_path_index)) != 0) {
        fdfs_client_destroy();
        fprintf(stderr, "tracker_query_storage fail, " \
            "error no: %d, error info: %s\n", \
            result, STRERROR(result));
        return result;
    }
    //TODO storage_upload_by_filename1查到了存储节点,就进行上传的动作
    //TODO file_id传出参数,如果上传成功了,就打印file_id
    result = storage_upload_by_filename1(pTrackerServer, \
            &storageServer, store_path_index, \
            local_filename, NULL, \
            NULL, 0, group_name, file_id);
    if (result == 0) {
        printf("%s\n", file_id);
    }
    else {
        fprintf(stderr, "upload file fail, " \
            "error no: %d, error info: %s\n", \
            result, STRERROR(result));
    }
    //TODO tracker_close_connection_ex与追踪器断开连接
    tracker_close_connection_ex(pTrackerServer, true);
    fdfs_client_destroy();
    return result;
}

8.3 两个方案的代码实现

8.3.1 源代码

  • fdfs_upload_file.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "fdfs_client.h"
#include <sys/wait.h>
//使用多进程方式
int upload_file1(const char *confFile, const char *uploadFile, char *fileID, int size) {
    //1. 创建管道
    int fd[2];
    int ret = pipe(fd);
    if (ret == -1) {
        perror("pipe error");
        exit(0);
    }
    //2. 创建子进程
    pid_t pid = fork();
    //子进程
    if (pid == 0) {
        //3. 标准输出重定向 -> pipe写端
        dup2(fd[1], STDOUT_FILENO);
        //4. 关闭读端
        close(fd[0]);
        execlp("fdfs_upload_file", "fdfs_upload_file", confFile, uploadFile, NULL);
        perror("execlp error");
    }
    else {
        //父进程读管道
        close(fd[1]);
        read(fd[0], fileID, size);
        //回收子进程的PCB
        wait(NULL);
    }
}
//使用fastDFS API实现
int upload_file2(const char *confFile, const char *myFile, char *fileID) {
    char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
    ConnectionInfo *pTrackerServer;
    int result;
    int store_path_index;
    ConnectionInfo storageServer;
    //TODO fdfs_client_init解析传入的配置文件
    if ((result = fdfs_client_init(confFile)) != 0) {
        return result;
    }
    //TODO tracker_get_connection在上面client配置文件里面
    //TODO 有追踪器的ip:port,所以这里就是连接追踪器,初始化pTrackerServer指针
    pTrackerServer = tracker_get_connection();
    if (pTrackerServer == NULL) {
        fdfs_client_destroy();
        return errno != 0 ? errno : ECONNREFUSED;
    }
    *group_name = '\0';
    //TODO tracker_query_storage_store追踪器 查询 存储节点 的 存储
    //TODO storageServer是传入参数,对storageServer赋值
    //TODO group_name是一个char数组,对group_name赋值
    //TODO store_path_index是传入参数,对store_path_index赋值
    if ((result = tracker_query_storage_store(pTrackerServer, \
                    &storageServer, group_name, &store_path_index)) != 0) {
        fdfs_client_destroy();
        fprintf(stderr, "tracker_query_storage fail, " \
            "error no: %d, error info: %s\n", \
            result, STRERROR(result));
        return result;
    }
    //TODO storage_upload_by_filename1查到了存储节点,就进行上传的动作
    //TODO file_id传出参数,如果上传成功了,就打印file_id
    result = storage_upload_by_filename1(pTrackerServer, \
            &storageServer, store_path_index, \
            myFile, NULL, \
            NULL, 0, group_name, fileID);
    if (result == 0) {
        printf("%s\n", fileID);
    }
    else {
        fprintf(stderr, "upload file fail, " \
            "error no: %d, error info: %s\n", \
            result, STRERROR(result));
    }
    //TODO tracker_close_connection_ex与追踪器断开连接
    tracker_close_connection_ex(pTrackerServer, true);
    fdfs_client_destroy();
    return result;
}
  • fdfs_upload_file.h
//
// Created by 68725 on 2022/9/5.
//
#ifndef FDFS_UPLOAD_FILE_H
#define FDFS_UPLOAD_FILE_H
extern int upload_file1(const char *confFile, const char *uploadFile, char *fileID, int size);
extern int upload_file2(const char *confFile, const char *myFile, char *fileID);
#endif //FDFS_UPLOAD_FILE_H
  • main.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include "fdfs_upload_file.h"
int main() {
    char fildID[1024] = {0};
    upload_file1("/etc/fdfs/client.conf", "main.c", fildID, sizeof(fildID));
    printf("Multiprocess upload_file1 fildID:%s\n", fildID);
    printf("=================================\n");
    upload_file2("/etc/fdfs/client.conf", "main.c", fildID);
    printf("Call API upload_file2 fildID:%s\n", fildID);
}

8.3.2 测试

root@wxf:/test# ls
fdfs_upload_file.c  fdfs_upload_file.h  main.c
root@wxf:/test# gcc -o test *.c -I/usr/include/fastdfs/ -lfdfsclient
root@wxf:/test# ls
fdfs_upload_file.c  fdfs_upload_file.h  main.c  test
root@wxf:/test# ./test 
Multiprocess upload_file1 fildID:group1/M00/00/00/wKhtZWMV4USAeXL4AAACCAMe_TM89174.c
=================================
group1/M00/00/00/wKhtZWMV4UWAeb7BAAACCAMe_TM19985.c
Call API upload_file2 fildID:group1/M00/00/00/wKhtZWMV4UWAeb7BAAACCAMe_TM19985.c

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
2月前
|
编译器 C++ 开发者
【Conan 入门教程 】使用Conan 2.X和Autotools高效构建C/C++项目
【Conan 入门教程 】使用Conan 2.X和Autotools高效构建C/C++项目
80 1
|
2月前
|
算法 测试技术 数据处理
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
51 1
|
2月前
|
算法 测试技术 开发工具
编写高效技术文档的艺术:C++项目实践指南
编写高效技术文档的艺术:C++项目实践指南
79 0
|
2月前
|
开发工具 C语言 C++
CMake构建大型C/C++项目:跨平台设计与高级应用(二)
CMake构建大型C/C++项目:跨平台设计与高级应用
46 0
|
28天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
56 7
|
1天前
|
存储 C++ UED
【C++】飞机大战项目记录
通过上面的设计,我们实现来看飞机大战的主要功能
14 4
|
1天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
4 0
|
12天前
|
C++
C++入门项目——通讯管理系统
C++入门项目——通讯管理系统
|
16天前
|
设计模式 存储 算法
C++从入门到精通:3.7阅读和理解开源项目——在实际项目中应用C++编程技巧和规范
C++从入门到精通:3.7阅读和理解开源项目——在实际项目中应用C++编程技巧和规范
|
1月前
C/C++test两步完成CMake项目静态分析
通过将C/C++test集成到CMake项目中,并根据项目的需要进行配置,可以在两步内完成CMake项目的静态分析。这样可以帮助开发人员及时发现并修复潜在的代码问题,提高代码质量和可靠性。
8 0

相关实验场景

更多