fastdfs是一个轻量级的分布式文件系统,主要由 tracker server, storage server 以及client组成,这里主要涉及两点 :
1)客户端上传文件流程和协议分析
2)实现一个简单的文件上传函数
一: 文件上传的基本流程
fastdfs中上传一个文件,主要涉及以下几个步骤:
1)上传连接请求,客户端会向tracker server发出上传文件的请求
2)tracker收到请求后,返回storage server的ip和端口
3)客户端连接storage,并且上传文件
4)文件上传完成后,storage返回路径信息
以下具体分析文件上传过程中的协议和各种操作
fastdfs协议头部:
typedef struct
{
char pkg_len[FDFS_PROTO_PKG_LEN_SIZE]; //body length, not including header(8个字节)
char cmd; //command code TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE
char status; //status code for response
} TrackerHeader;
fastdfs协议的头部是由10个字节大小的结构体构成,
发送:发送数据时,先发送TrackerHeader到服务器,随后发送具体的数据
接受:接受数据时,先接受sizeof(TrackerHeader)大小的报文头部,随后接受pkg_len长度的报文体
status: 发送的时候设置为0
cmd: 命令
pkg_len:一个int64_t的整型,除去TrackerHeader长度的报文长度
二: 客户端向tracker server发送获取storage地址请求
#define TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE 101
// 协议头
// pkg_len | cmd | status
// 8 bytes | 1 bytes | 1 bytes
//向tracker server请求storage server cmd
#define TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE 101
TrackerHeader header;//协议头部
memset(&header, 0, sizeof(TrackerHeader));
header.cmd = TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
//向tracker server 请求 storage,tcpsenddata 返回非0,表示发送成功
if(tcpsenddata(sockfd, &header, sizeof(TrackerHeader), 10, &count) != 0)
{
fprintf(stderr, "tcpsenddata error: %s\n", strerror(errno));
return 1;
}
else//请求发送成功,等待tracker回复
{
//接收头部,头部是一个TrackerHeader类型,10个字节
TrackerHeader resp;
if((ret_code = tcprecvdata(sockfd, &resp, sizeof(TrackerHeader), 10, &count)) != 0)
{
fprintf(stderr, "tcprecvdata error: %s\n", strerror(ret_code));
return 1;
}
//开始接收报文体
//int64_t read_int64(const char *buff)
//{
// unsigned char *p;
// p = (unsigned char *)buff;
// return (((int64_t)(*p)) << 56) | \
// (((int64_t)(*(p+1))) << 48) | \
// (((int64_t)(*(p+2))) << 40) | \
// (((int64_t)(*(p+3))) << 32) | \
// (((int64_t)(*(p+4))) << 24) | \
// (((int64_t)(*(p+5))) << 16) | \
// (((int64_t)(*(p+6))) << 8) | \
// ((int64_t)(*(p+7)));
//}
int size = read_int64(resp.pkg_len);//获取报体长度
char *buf = (char*)calloc(size + 1, sizeof(char));
if((ret_code = tcprecvdata(sockfd, buf, size, 10, &count) != 0))
{
fprintf(stderr, "tcprecvdata error: %s\n", strerror(ret_code));
return 1;
}
// 报文体
// group_name |ip |port |storage_index
// 16 bytes |16 bytes |8 bytes |
//#define TRACKER_QUERY_STORAGE_STORE_BODY_LEN 40
if(count != TRACKER_QUERY_STORAGE_STORE_BODY_LEN)
{
fprintf(stderr, "invalid message");
return 1;
}
//group name
//#define FDFS_GROUP_NAME_MAX_LEN 16
char group_name[FDFS_GROUP_NAME_MAX_LEN + 1] = {
0};
memcpy(group_name, buf, FDFS_GROUP_NAME_MAX_LEN);
group_name[FDFS_GROUP_NAME_MAX_LEN] = '\0';
//ip: port
//#define IP_ADDRESS_SIZE 16
//port:8 bytes
char ip[IP_ADDRESS_SIZE + 1] = {
0};
memcpy(ip, buf + FDFS_GROUP_NAME_MAX_LEN, IP_ADDRESS_SIZE - 1);
char szPort[8] = {
0};
memcpy(szPort, buf + FDFS_GROUP_NAME_MAX_LEN + IP_ADDRESS_SIZE - 1, 8);
ip[IP_ADDRESS_SIZE] = '\0';
int port = read_int64(szPort);
//storage index;
char *storage_index = buf + FDFS_GROUP_NAME_MAX_LEN + IP_ADDRESS_SIZE - 1 + FDFS_PROTO_PKG_LEN_SIZE;
三:以上步骤完成后,获取storage的ip 和 port后,就可以上传文件了
在官方的客户端中,文件操作有upload,download, append,delete等,这里只涉及upload
上传文件中,官方给出了三种方式
1)通过buffer上传,即将文件读取进内存,然后在发送
2)使用sendfile,sendfile是Linux提供的一个库函数
3)通过回调函数的方式
这里主要涉及的是第一种,通过buffer上传的方式
文件上传协议:
//文件上传协议头部
10 bytes | 1 bytes | 8 bytes | 6 bytes |
TrackerHeader | storage_index | 文件长度 | 文件名或者全为0) |
//storage_index 是客户端向tracker server申请storage index时候返回的结果
//文件名 如果不为空,那么取前6位,或者可以全部设置为0
//上传完成 storage回复客户端协议
10 bytes | 16 bytes | TrackerHeader.pkg_len - 16bytes
TrackerHeader | groupname | remote fi