C++文件服务器项目—FastDFS—1
- 前言
- 1. 项目架构
- 2. 分布式文件系统
-
- 3. FastDFS介绍
- 3.1 fdfs概述
- 3.2 fdfs框架中的三个角色
- 3.3 fdfs三个角色之间的关系
- 3.4 fdfs集群
- 4. FastDFS的安装
- 4.1 源码安装详解
- 4.2 安装源码
- 4.3 make install做了哪些事
- 4.4 测试
- 5. fdfs的配置文件
- 5.1 tracker配置文件
- 5.2 storage配置文件
- 5.3 client配置文件
- 5.4 集群的配置
- 6. fdfs的启动
- 6.1 第一个启动追踪器 - 守护进程
- 6.2 第二个启动存储节点 - 守护进程
- 6.3 第三个启动客户端 - 普通进程
- 6.4 FastDFS的状态检测
- 7. file_id的构成
- 8. 上传下载的代码实现
- 8.1 使用多进程方式-思路
- 8.2 使用fastDFS API实现-思路
- 8.3 两个方案的代码实现
-
前言
本项目使用c++实现一个文件服务器,核心功能是上传与下载。所以该项目可以作为网盘,也可以作为图床来用。
本文的核心重点是介绍FastDFS
的概念
、构成
、配置文件
、启动
与上传下载的实现
。后续将逐步介绍nginx,mysql,redis,fastcgi等内容。
本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。
1. 项目架构
- 客户端-网络架构
- b/s:浏览器使用http协议
- c/s:客户端可以随意选择协议
- 反向代理服务器
- 客户端并不能直接访问web服务器, 直接访问到的是反向代理服务器
- 客户端静请求发送给反向代理服务器, 反向代理将客户端请求转发给web服务器
- 服务端
-
Nginx
- 可以处理静态请求:html,jpg
- 无法处理动态请求
- 服务器部署为集群后, 每台服务器上部署的内容必须相同
-
fastCGI
- 关系型数据库mysql
- 非关系型数据库redis
- 提高程序效率,减轻mysql压力
- 存储是服务器经常要从关系型数据中读取的数据
FastDFS - 分布式文件系统
2. 分布式文件系统
2.1 传统文件系统
传统的文件系统可以被挂载和卸载,格式:ntfs / fat32 / ext3 / ext4
一台主机的磁盘插槽不可能是无限的,当磁盘都满了之后想要扩充,而磁盘插槽都被插满了,那么这个时候就需要使用分布式文件系统了。
2.2 分布式文件系统
完整的文件系统, 不在同一台主机上,而是在很多台主机上,多个分散的文件系统组合在一起,形成了一个完整的文件系统。
分布式文件系统:
- 需要有网络
- 多台主机(不需要在同一地点)
- 需要管理者,管理所有的存储节点
- 编写应用层的管理程序(Ceph,TFS,FastDFS,MogileFS,MooseFS,GlusterFS)
3. FastDFS介绍
3.1 fdfs概述
- 是用c语言编写的一款开源的分布式文件系统。
- 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,注重高可用、高性能等指标
- 可以很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
3.2 fdfs框架中的三个角色
- 追踪器 ( tracker ) - 管理者 - 守护进程
- 存储节点(storage )- 守护进程
- 客户端(client) - 不是守护进程, 这是程序猿编写的程序
3.3 fdfs三个角色之间的关系
上传
下载
-
追踪器 (tracker)
- 最先启动追踪器(存储节点和客户端都需要主动连接追踪器)
-
存储节点(storage )
- 第二个启动的角色
- 存储节点启动之后, 会单独开一个线程
- 汇报当前存储节点的容量, 和剩余容量
- 汇报数据的同步情况
- 汇报数据被下载的次数
-
客户端
- 最后启动
- 上传
- 连接追踪器, 询问存储节点的信息
- 我要上传1G的文件, 询问哪个存储节点有足够的容量
- 追踪器查询, 得到结果
- 追踪器将查到的存储节点的IP+端口发送给客户端
- 通过得到IP和端口连接存储节点
- 将文件内容发送给存储节点
- 下载
- 连接追踪器, 询问存储节点的信息
- 问一下, 要下载的文件在哪一个存储节点
- 追踪器查询, 得到结果
- 追踪器将查到的存储节点的IP+端口发送给客户端
- 通过得到IP和端口连接存储节点
- 下载文件
3.4 fdfs集群
- 追踪器集群
-
为什么需要追踪器集群?
-
多个tracker如何工作?
-
如何实现集群
-
存储节点集群
-
fastDFS如何管理存储节点?
-
集群方式(两种扩容方式)
-
如何实现集群
横向扩容 - 增加容量:
假设当前有两个组: group1, group2
需要添加一个新的分组 -> group3
新主机属于group3
添加一台新的主机 -> 容量增加了
不同组的主机之间不需要通信
纵向扩容 - 数据备份:
假设当前有两个组: group1, group2
将新的主机放到现有的组中
每个组的主机数量从1 -> N
- 这n台主机的关系就是相互备份的关系
- 同一个组中的主机需要通信
- 每个组的容量 等于 组内容量最小的主机容量(比如Group1的容量是500G)
4. FastDFS的安装
4.1 源码安装详解
安装流程:
- 以下文件, 里边有安装步骤
- 找 可执行文件 configure
- 执行make命令
- 安装 make install (需要管理员权限)
- 将第三步生成的动态库/动态库/可执行程序拷贝到对应的系统目录
4.2 安装源码
- 安装 libfastcommon(fastdfs的基础库包)
git clone https:
cd libfastcommon
./make.sh
./make.sh install
- 安装 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:/
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
root@wxf:/
/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
/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
client.conf storage.conf storage_ids.conf tracker.conf
5.1 tracker配置文件
不同的tracker主机需要各自修改自己的配置文件
bind_addr=192.168.109.100
port=22122
base_path=/fastdfs/tracker
这里因为fastdfs由client,storage,tracker构成,所以可以创建一个fastdfs目录,里面再分三个子目录,由一个大目录fastdfs进行统一管理。
5.2 storage配置文件
不同的storage主机需要各自修改自己的配置文件
group_name=group1
bind_addr=
port=23000
base_path=/fastdfs/storage
store_path_count=2
store_path0=/fastdfs/storage0
store_path1=/fastdfs/storage1
tracker_server=192.168.109.100:22122
tracker_server=192.168.109.101:22122
上面配置两个存储目录和两个追踪器地址只是演示怎么配置而已,后文用的配置如下
base_path=/fastdfs/storage
store_path_count=1
store_path0=/fastdfs/storage
tracker_server=192.168.109.100:22122
5.3 client配置文件
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 第一个启动追踪器 - 守护进程
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
6.2 第二个启动存储节点 - 守护进程
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf stop
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
6.3 第三个启动客户端 - 普通进程
root@wxf:/
group1/M00/00/00/wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
root@wxf:/
root@wxf:/
wKhtZWMVtDGAR4d0AAAHTaCnXjs12.conf
6.4 FastDFS的状态检测
- 实现使用工具查看一下是否启动成功
netstat -apn | grep 80
sudo lsof -i:80
ps -aux | grep fdfs
lsof -i:22122
lsof -i:23000
ps aux|grep fdfs_
- 使用fdfs_monitor检测fastDFS状态
fdfs_monitor /etc/fdfs/client.conf
可以看到fdfs_monitor 后面跟的是client的配置信息,所以可以把它当作一个客户端,启动时先连接追踪器,向追踪器询问storage的信息
STORAGE SERVER的状态通常有七种,正常状态必须是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(),释放子进程的资源。
-
操作步骤
-
创建管道 - pipe
-
创建子进程
-
子进程干什么?
- 写管道, 关闭读端
- 重定向
- 执行execl命令, 调用另外的进程fdfs_upload_file
- 子进程退出
-
父进程?
8.2 使用fastDFS API实现-思路
作者并没有提供api与文档出来,但是我们可以通过fdfs_upload_file.c
,看看源码是怎么做的。
看我写的//TODO 注释
即可。那么我们的思路就很简单,把这个main函数改成一个接口即可。
#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];
if ((result = fdfs_client_init(conf_filename)) != 0) {
return result;
}
pTrackerServer = tracker_get_connection();
if (pTrackerServer == NULL) {
fdfs_client_destroy();
return errno != 0 ? errno : ECONNREFUSED;
}
local_filename = argv[2];
*group_name = '\0';
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;
}
}
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;
}
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));
}
tracker_close_connection_ex(pTrackerServer, true);
fdfs_client_destroy();
return result;
}
8.3 两个方案的代码实现
8.3.1 源代码
#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) {
int fd[2];
int ret = pipe(fd);
if (ret == -1) {
perror("pipe error");
exit(0);
}
pid_t pid = fork();
if (pid == 0) {
dup2(fd[1], STDOUT_FILENO);
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);
wait(NULL);
}
}
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;
if ((result = fdfs_client_init(confFile)) != 0) {
return result;
}
pTrackerServer = tracker_get_connection();
if (pTrackerServer == NULL) {
fdfs_client_destroy();
return errno != 0 ? errno : ECONNREFUSED;
}
*group_name = '\0';
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;
}
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));
}
tracker_close_connection_ex(pTrackerServer, true);
fdfs_client_destroy();
return result;
}
#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
#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
fdfs_upload_file.c fdfs_upload_file.h main.c
root@wxf:/test
root@wxf:/test
fdfs_upload_file.c fdfs_upload_file.h main.c test
root@wxf:/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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)