1:数据库的设计与框架大纲
- 百度网盘的功能实现,肯定是需要借助数据库存储文件信息,和用户信息的,以及多点下载和反向代理、负载均衡。打算手撕一波百度网盘,来作为我的毕设项目。
- 设计的主要关键点在于数据库表的设计,我需要用到一个虚拟文件系统,用户根据owner_id来改变当前的工作目录和对自己的文件进行增删查改。
- ls
- cd
- pwd
- puts filename
- gets filename
- mkdir filename
- rm filename
- 文件秒传
- 用户注册验证
- VIP功能
- 日志功能
- 待实现功能:断点续传,长短命令分离,反向代理,负载均衡下的多点下载,令牌验证。
2:全局框架流程图
**
3:用户命令基于服务器对数据库进行操作
**
ls命令接口,通过查询数据库,查询当前工作目录下,所有文件,并且是该用户的文件。
select filename from file where owner_id='1' and parent_id='1';
#设计file表时,用户登录后起始为/目录,1
cd命令接口,通过查询数据库,当前工作目录有没有该目录文件。
#cd file
select filename from file where owner_id='1' and filename='file' and type=1;
#有当前工作目录,将user表的pwd当前工作目录进行Update
#注意,更改了当前工作目录,程序中parent_id=1,需要进行修改了
update user set pwd='file' where user_id='1';
pwd这个接口比较简单
select pwd from user where owner_id='1';
#将查询到的结果直接send即可
最关键的是puts和gets命令,实现文件的并行下载和上传,这些都需要一定时间,是属于长命令,直接将队列中保存的通信文件描述符,和队列移交给线程池,属于共享资源,需要每个线程去争抢。
**
4:任务队列的设计
**
typedef struct
{
int size;//存储任务节点的个数
pNode_t pHead;//头节点
pNode_t Ptail;//尾节点
pthread_cond_t cond;//条件变量
int flag;//退出标志
pthread_mutex_t mutex;//互斥量
}Que_t,*pQue_t;
多个线程池内的线程共享唯一的队列,队列中有每个任务的任务节点。
typedef struct node
{
int clientFd;//存储客户端的fd
char order[100];//存储客户端要执行的命令
char file[100];//存储客户端要操作的文件名
int parent_id;
int userid;
struct node* next;
}Node_t,*pNode_t;
通过任务节点中的数据域判断,是需要执行上传还是下载,为了拓展性,以后可能不止上传和下载的长命令任务。
//负责命令解析的线程处理函数
//主线程只负责监听新连接
#include "../include/head.h"
#include "../include/workque.h"
#include "../include/md5.h"
#include "../include/thread_Pool.h"
threadPool_t pool;
int exitPipe[2];
typedef struct clientid
{
int cfd;
Que_t *que;//全局唯一队列
}Client_t;
Client_t client[1024] = {0}; //保存最大连接数量
size_t cut = 0;
void* func(void *arg)
{
puts("成功创建一个处理文件描述符的子线程\n");
char parent_id[5]="1";
Client_t c1 = *(Client_t*)arg;
//通过主线程得到移交过来的命令和负责通信的套接字
int userId = serverLogin(c1.cfd);
//登录接口
int epd=epoll_create(2);
epollAddFd(c1.cfd,epd);
int sum=0;
struct epoll_event evs[2];
while(1)
{
sum = epoll_wait(epd,evs,2,-1);
for(int i=0 ; i<sum; i++)
{
if(evs[i].data.fd == c1.cfd)
{
char arr[50]={0};
int ret1 = recv(c1.cfd, arr, sizeof(arr), 0);
//arr存储了客户端发送过来的命令
//直接在子线程中进行命令解析
if (ret1 == 0)//客户端断开连接防止刷屏
//具体可看man recv返回值
{
break;
}
//ls查看当前工作目录下的所有文件
if (!(strcmp(arr, "ls")))
{
char temp[5] = {0};
sprintf(temp,"%d",userId);
printf("处理客户端的ls\n");
ls(c1.cfd, parent_id, temp);
record log;
strcpy(log.username, temp);
strcpy(log.cmd, arr);
insert_log(log);//写入日志文件中
}
if (!(strcmp(arr, "pwd")))
{
char str[20];
sprintf(str, "%d", userId);
pwd(str, c1.cfd,arr);
}
char *ptmp = strchr(arr, ' '); //走到中间的空格停下来
//命令解析,遇到空格,就截断成左右两个字符串
if (ptmp != NULL)
{
char buf[20] = {0};
strncpy(buf, arr, (int)(ptmp - arr)); //截取命令左边的
if (!(strcmp(buf, "cd"))) //如果是cd命令
{
//需要截取cd右边的参数
char data[20] = {0};
strcpy(data, ptmp + 1);
Rtrim(data);
//char parent[20];
//sprintf(parent,"%d",parent_id);
char temp[10];
sprintf(temp,"%d",userId);
cd(c1.cfd, data,parent_id,temp);
printf("cd 之后的parent_id = %s\n",parent_id);
//需要修改当前工作目录的
record log;
strcpy(log.username, temp);
strcpy(log.cmd, arr);
insert_log(log);
}
if(!(strcmp(buf,"puts")))
{
//上传文件
char data[40]={0};
strcpy(data,ptmp+1);//截取右边的命令
Rtrim(data);
printf("需要上传文件%s\n",arr);
pNode_t pNew = (pNode_t)calloc(1, sizeof(Node_t));
pNew->userid=userId;
pNew->clientFd=c1.cfd;
pNew->parent_id=atoi(parent_id);
strcpy(pNew->order,buf);
pthread_mutex_lock(&c1.que->mutex);
queInsert(&pool.Que, pNew);
//任务队列里面有数据了,唤醒等待的子线程
pthread_cond_signal(&c1.que->cond);
pthread_mutex_unlock(&c1.que->mutex);
}
if (!(strcmp(buf, "gets")))
{
char data[20] = {0};
strcpy(data,ptmp+1);
printf("data是 %s\n",data);
pNode_t pNew = (pNode_t)calloc(1, sizeof(Node_t));
char str[20];
sprintf(str, "%d", userId);
if(selectFilename(data,str)==0)
{
break;
}
pNew->clientFd = c1.cfd;
strcpy(pNew->order,buf);//客户端要执行的命令
strcpy(pNew->file,data);//客户端要操作的文件名
pthread_mutex_lock(&(c1.que->mutex));
//把节点插入到任务队列里面
queInsert(c1.que, pNew);
//任务队列里面有数据了,唤醒等待的子线程
pthread_cond_signal(&c1.que->cond);
pthread_mutex_unlock(&c1.que->mutex);
record log;
strcpy(log.username, str);
strcpy(log.cmd, arr);
insert_log(log);
}
}
}
}
}
}
主线程
void sigFunc(int sig)
{
printf("触发信号函数,给子进程管道写入flag\n");
write(exitPipe[1], &sig, 4);
}
int main()
{
//int parent_id=1;
char parent_id[10] = "1";
pConf_t conf = (pConf_t)calloc(1, sizeof(Conf_t));
pConf_t phead;
phead = load("tcp.conf", &conf);
//打开配置文件,读取配置项
char ip[20], port[5];
char num[4];
int ret;
int str = atoi(phead->next->next->ip);
printf("ip = %s\tport = %s\n", phead->ip, phead->next->ip);
printf("str : %d\n", str);
pipe(exitPipe);
if (fork())
{
close(exitPipe[0]);
signal(SIGUSR1, sigFunc);
wait(NULL);
}
//threadPool_t pool;
threadPoolInit(&pool, str);
//2.启动线程池
threadPoolStart(&pool);
//3.创建tcp监听套接字
int sfd = 0;
tcpInit(phead->ip, phead->next->ip, &sfd);
//4.创建epoll
int epfd = epoll_create(1);
epollAddFd(sfd, epfd);
epollAddFd(exitPipe[0], epfd);
//将管道读端纳入监听队列
struct epoll_event evs[2];
int newFd = 0;
int sums = 0;
while (1)
{
sums = epoll_wait(epfd, evs, 2, -1);
for (int i = 0; i < sums; ++i)
{
if (evs[i].data.fd == sfd)
{
newFd = accept(sfd, NULL, NULL);
client[cut].cfd=newFd;
client[cut].que=&pool.Que;
pthread_t tid;
printf("成功建立连接\n");
//创建任务节点,newFd交给节点
pthread_create(&tid,NULL,func,(void*)&client[cut++]);
}
else if (evs[i].data.fd == exitPipe[0])
{
for (int a = 0; a < str; a++)
{
//循环遍历所有子线程
pool.Que.flag = 0;
}
for (int a = 0; a < str; a++)
{
pthread_join(pool.pthid[a], NULL);
}
}
}
}
return 0;
}
**
5:配置文件配置项
**
//给出头文件声明
typedef struct conf1
{
char ip[17];
struct conf1* next;
}Conf_t,*pConf_t;
typedef struct confNode
{
int size;
pConf_t phead;
pConf_t ptail;
}confNode_t,*pconfNode_t;
//截取字符串前的空格
void Ltrim(char *string);
void Rtrim(char *string);
pConf_t load(const char *pconfName, pConf_t* conf);
Ltrim实现,截取配置文件中头部的空格
void Ltrim(char *string)
{
size_t len = 0;
len = strlen(string);
char *p_tmp = string;
if ((*p_tmp) != ' ') //不是以空格开头
return;
//找第一个不为空格的
while ((*p_tmp) != '\0')
{
if ((*p_tmp) == ' ')
p_tmp++;
else
break;
}
if ((*p_tmp) == '\0') //全是空格
{
*string = '\0';
return;
}
char *p_tmp2 = string;
while ((*p_tmp) != '\0')
{
(*p_tmp2) = (*p_tmp);
p_tmp++;
p_tmp2++;
}
(*p_tmp2) = '\0';
return;
}
Rtrim实现截取字符串后面的空格
//截取合法字符串后的空格
void Rtrim(char *string)
{
size_t len = 0;
if (string == NULL)
return;
len = strlen(string);
while (len > 0 && string[len - 1] == ' ') //位置换一下
string[--len] = 0;
return;
}
主要实现,将每一项配置都合理读出,忽略注释行,存储到链表中,如果用C++,无疑是vector比较舒服的。下文会写出C++方式实现。
pConf_t load(const char *pconfName, pConf_t* conf)
{
//pConf_t *pcur;
int sum = 0;
pconfNode_t pcur = (pconfNode_t)calloc(1, sizeof(confNode_t));
FILE *fp;
fp = fopen(pconfName, "r");
if (fp == NULL)
return NULL;
//每一行配置文件读出来都放这里
char linebuf[501];
while (!feof(fp))
{
if (fgets(linebuf, 500, fp) == NULL)
continue;
if (linebuf[0] == 0)
continue;
//处理注释行
if (*linebuf == ';' || *linebuf == ' ' || *linebuf == '#' || *linebuf == '\t' || *linebuf == '\n')
continue;
lblprocstring:
//屁股后边若有换行,回车,空格等都截取掉
if (strlen(linebuf) > 0)
{
if (linebuf[strlen(linebuf) - 1] == 10 || linebuf[strlen(linebuf) - 1] == 13 || linebuf[strlen(linebuf) - 1] == 32)
{
linebuf[strlen(linebuf) - 1] = 0;
goto lblprocstring;
}
}
if (linebuf[0] == 0)
continue;
if (*linebuf == '[') //[开头的也不处理
continue;
//==0肯定就是没读到数据
char *ptmp = strchr(linebuf, '=');
//走到=停下来
//需要截取右边的
if (ptmp != NULL)
{
strcpy((*conf)->ip,ptmp + 1);
Rtrim((*conf)->ip);
Ltrim((*conf)->ip);
}
pConf_t cf = (pConf_t)calloc(1, sizeof(Conf_t));
strcpy(cf->ip, (*conf)->ip);
if (pcur->size == 0)
{
pcur->phead = cf;
pcur->ptail = cf;
pcur->size++;
}
else
{
pcur->ptail->next = cf;
pcur->ptail = cf;
pcur->size++;
}
}
pConf_t p;
p = pcur->phead;
while (pcur->phead)
{
printf("%s\n", pcur->phead->ip);
pcur->phead = pcur->phead->next;
}
return p;
}
C++优雅实现配置文件配置项读取
typedef struct _CConfItem
{
char ItemName[50];//配置项名
char ItemContent[500];//配置项内容
}CConfItem,*LPCConfItem;
单例类声明
class CConfig
{
private:
CConfig();
public:
~CConfig();
private:
static CConfig *m_instance;//静态类指针变量需要通过静态成员方法去被初始化
public:
//静态成员方法,初始化单例类
static CConfig* GetInstance()
{
if(m_instance == NULL)
{
//锁
if(m_instance == NULL)
{
m_instance = new CConfig();
static CGarhuishou cl;
}
//放锁
}
return m_instance;
}
class CGarhuishou //类中套类,用于释放对象
{
public:
~CGarhuishou()
{
if (CConfig::m_instance)
{
delete CConfig::m_instance;
CConfig::m_instance = NULL;
}
}
};
public:
bool Load(const char *pconfName); //加载配置文件
const char *GetString(const char *p_itemname);//获取配置项名
int GetIntDefault(const char *p_itemname,const int def);//获取配置文件配置项后的配置信息
public:
std::vector<LPCConfItem> m_ConfigItemList;
//对结构体指针类型进行存储,存储了配置信息
};
main函数调用
int main()
{
CConfig *p_config = CConfig::GetInstance(); //单例类
if(p_config->Load("MyConf.conf") == false)
{
return 0;
}
}
类成员函数的实现,Load
//装载配置文件
bool CConfig::Load(const char *pconfName)
{
FILE *fp;
fp = fopen(pconfName,"r");
if(fp == NULL)
return false;
//每一行配置文件读出来都放这里
char linebuf[501];
//走到这里,文件打开成功
while(!feof(fp))
{
if(fgets(linebuf,500,fp) == NULL) //从文件中读数据
continue;
if(linebuf[0] == 0)
continue;
//处理注释行
if(*linebuf==';' || *linebuf==' ' || *linebuf=='#' || *linebuf=='\t'|| *linebuf=='\n')
continue;
lblprocstring:
//截取字符串后面的空格,回车等,尽量考虑所有情况
if(strlen(linebuf) > 0)
{
if(linebuf[strlen(linebuf)-1] == 10 || linebuf[strlen(linebuf)-1] == 13 || linebuf[strlen(linebuf)-1] == 32)
{
linebuf[strlen(linebuf)-1] = 0;
goto lblprocstring;
}
}
if(linebuf[0] == 0)
continue;
if(*linebuf=='[') //忽略[]
continue;
char *ptmp = strchr(linebuf,'=');
if(ptmp != NULL)
{
LPCConfItem p_confitem = new CConfItem; //注意前边类型带LP,后边new这里的类型不带
memset(p_confitem,0,sizeof(CConfItem));
strncpy(p_confitem->ItemName,linebuf,(int)(ptmp-linebuf)); //等号左侧的拷贝到p_confitem->ItemName
strcpy(p_confitem->ItemContent,ptmp+1); //等号右侧的拷贝到p_confitem->ItemContent
Rtrim(p_confitem->ItemName);
Ltrim(p_confitem->ItemName);
Rtrim(p_confitem->ItemContent);
Ltrim(p_confitem->ItemContent);
m_ConfigItemList.push_back(p_confitem); /
}
}
fclose(fp);
return true;
}
GetString实现
const char *CConfig::GetString(const char *p_itemname)
{
std::vector<LPCConfItem>::iterator pos; //用与存储
for(pos = m_ConfigItemList.begin(); pos != m_ConfigItemList.end(); ++pos)
{
if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
return (*pos)->ItemContent;
//strcasecmp不是Windows函数,注意移植性,用与比较时忽略大小写
//不是C/C++的标准头文件
}
return NULL;
}
获取配置项信息GetIntDefault实现
int CConfig::GetIntDefault(const char *p_itemname,const int def)
{
std::vector<LPCConfItem>::iterator pos;
for(pos = m_ConfigItemList.begin(); pos !=m_ConfigItemList.end(); ++pos)
{
if(strcasecmp( (*pos)->ItemName,p_itemname) == 0)
return atoi((*pos)->ItemContent);//将字符串读出转化成数字
}
return def;
}
需要任何配置项时,再遍历这个容器即可。
例如
if(p_config->GetIntDefault("Daemon",0) == 1);
线程池模型图和代码链接
线程池文件下载
**
6:各命令具体实现
ls
void ls(int fd,char *parent_id,char* userId)
{
//文件描述符,父id,用户id
char query[300]="select filename from file where owner_id='";
//2 and parent_id=15
strcat(query,userId);
strcat(query,"' and parent_id='");
strcat(query,parent_id);
strcat(query,"'");
printf("ls 查询语句为:%s\n",query);
selectls(query,fd);
}
cd
void cd(int fd,char* data,char *parent_id,char* userid)
{
//文件描述符 cd 右边的参数 parent_id父节点需要改变
//printf("main.c%s\n",data);
//dup2(fd,1);
//cddata(1,data);
//cd需要修改当前工作目录,修改之前要判断数据库该用户有没有该目录
//select filename from file where owner_id='2' and filename='arr2';
char query[300]="select filename from file where owner_id='";
strcat(query,userid);
strcat(query,"' ");
strcat(query,"and filename='");
strcat(query,data);
strcat(query,"'");
strcat(query," and type=1");
printf("客户端的查询命令是%s\n",query);
if(selectcd(query,data)==1)
{
printf("客户端有此文件\n");
//修改当前工作目录
//update member set age = 15 where id = 1;
char query[300]="update user set pwd='";
strcat(query,data);
strcat(query,"'");
strcat(query,"where id='");
strcat(query,userid);
strcat(query,"'");
printf("pwd = %s\n",query);
updatePwd(query);
//修改传进来的parent_id
//char query1[300]="select id from file where owner_id=1 and parent_id='";
//sprintf(query1,"%s%s' and type=1 and filename='%s'",query1,parent_id,data);
char query1[300]="select id from file where owner_id='";
sprintf(query1,"%s%s' and parent_id='%s' and type=1 and filename='%s'",query1,userid,parent_id,data);
printf("update select = %s\n",query1);
if(updateparent_id(query1)!=NULL)
{
strcpy(parent_id,updateparent_id(query1));
char* sed="yes";
send(fd,sed,strlen(sed),0);
}
}
else
{
char *arr="The client does not have the file, or it is a plain file";
send(fd,arr,strlen(arr),0);
}
//先查数据库有没有这个filename
}
pwd
void pwd(char* userId,int fd,char* pwd)
{
//查询user表
printf("查询user 表找pwd\n");
char query[300]="select pwd from user where id='";
strcat(query,userId);
strcat(query,"'");
selectpwd(query,fd);
}
gets filename
assist函数功能为:查询当前用户有没有此文件
int selectFilename(char *filename,char* userID)
{
//根据传入参数,查询数据
char query[300]="select filename from file where owner_id='";
strcat(query,userID);
strcat(query,"'");
int ret = assist(query,filename);
return ret;
}
#include "../include/head.h"
int assist(char *query,char* filename)
{
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
char *server = "localhost";
char *user = "chen";
char *password = "123";
char *database = "37chen";//要访问的数据库名称
//char query[300] = "select * from user where id='";
unsigned int queryRet;
//strcpy(query,"select * from user");
//在输出前先打印查询语句
//初始化
conn = mysql_init(NULL);
if(!conn)
{
printf("MySQL init failed\n");
return -1;
}
//连接数据库,看连接是否成功,只有成功才能进行后面的操作
if(!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
{
printf("Error connecting to database: %s\n", mysql_error(conn));
return -1;
}
else
{
printf("MySQL Connected...\n");
}
//把SQL语句传递给MySQL
queryRet = mysql_query(conn, query);
if(queryRet)
{
printf("Error making query: %s\n", mysql_error(conn));
}
else
{
//用mysql_num_rows可以得到查询的结果集有几行
//要配合mysql_store_result使用
//第一种判断方式
res = mysql_store_result(conn);
printf("mysql_num_rows = %lu\n", (unsigned long)mysql_num_rows(res));
//第二种判断方式,两种方式不能一起使用
/* res = mysql_use_result(conn); */
row = mysql_fetch_row(res);
if(NULL == row)
{
printf("Don't find any data\n");
}
else
{
do
{
/* printf("num=%d\n",mysql_num_fields(res));//列数 */
//每次for循环打印一整行的内容
for(queryRet = 0; queryRet < mysql_num_fields(res); ++queryRet)
{
if(!(strcmp(row[queryRet],filename)))
{
//如果找到了就,直接返回1
printf("客户端有该文件\n");
return 1;
}
//printf("%8s ", row[queryRet]);
}
printf("\n");
}while(NULL != (row = mysql_fetch_row(res)));
}
mysql_free_result(res);
}
mysql_close(conn);
return 0;
}
puts filename 上传文件
server_gets.c
#define _GNU_SOURCE
#include "../include/head.h"
#include "../include/md5.h"
#define MAX 1000
typedef struct{
int dataLen;
char buf[1000];
}Train_t;
int serverGets(int sockFd, int curDirID, int ownerID)
{
int dataLen = 0;
int fileSize = 0;
char fileName[64] = {0};
char md5str[128] = {0};
Train_t train;
memset(&train,0,sizeof(train));
recv(sockFd,&train.dataLen,4,MSG_WAITALL);
printf("filename len = %d\n",train.dataLen);
recv(sockFd,train.buf,train.dataLen,MSG_WAITALL);
printf("filename is %s\n",train.buf);
int fd = open(train.buf,O_RDWR|O_CREAT,0666);
off_t totalsize;
recv(sockFd,&totalsize,sizeof(off_t),MSG_WAITALL);
ftruncate(fd,totalsize);
printf("filesize is %ld\n",totalsize);
char *p =(char*)mmap(NULL,totalsize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
ERROR_CHECK(p,(char*)MAP_FAILED,"mmap");
recv(sockFd,p,totalsize,MSG_WAITALL);
munmap(p,totalsize);
return 0;
}
主要用于零拷贝接口,客户端为sendfile
**
**
7:用户加密登录功能
gcc 编译该程序时需要加入 -lcrypt
功能说明
服务器先传输 salt 值给客户端,客户端 crypt 加密后,传输密码密
文给服务器,服务器进行匹配后,判断是否成功。
Linux 引入了 salt,所谓 salt 即为一个随机数,引入的时候为一个 12bit 的数值,当用户设置密码时,会随机生成一个 salt 与用户的密码一起加密,得到一个加密的字符串(salt 以明文形式包含在该字符中),存储到密码文件中。crypt 函数可以将用户输入的密码和 salt 一起适应某种算法进行加密,该加密后的字符串就是我们需要的与密码文件中密
码对比的加密字符串。
md5加密算法现在已经过时
crypt 为支持不同的方式将 salt 格式化为
i
d
id
idsalt$encode
其中 id 代表不同的算法
1 | MD5
2a | Blowfish (not in mainline glibc; added in some
| Linux distributions)
5 | SHA-256 (since glibc 2.7)
6 | SHA-512 (since glibc 2.7) 当前我们 Linux 采用的加密算法
qt中也封装了不少哈希函数,运用方式比这个Linux的简单许多
int serverLogin(int sockFd)
{
//1、先接收客户端发来的用户名,然后去数据库里面找
// 如果在数据库中,把相应的盐值发给客户端
// 如果不在,通知客户端重新输入
char usrname[20] = {0};
int sendRet = 0;
char flag[2] = {0};
int recvRet = recv(sockFd, usrname, sizeof(usrname), 0);
ERROR_CHECK(recvRet, -1, "recv");
char salt[9] = {0};
char pwd[128] = {0};
int userID = query(usrname, salt, pwd);
ERROR_CHECK(userID, -1, "query");
while(userID == 0){
//非法用户名,通知客户端重新输入
flag[0] = 'u';
sendRet = send(sockFd, flag, 1, 0);
ERROR_CHECK(sendRet, -1, "send");
//重新接收
memset(usrname, 0, sizeof(usrname));
recvRet = recv(sockFd, usrname, sizeof(usrname), 0);
ERROR_CHECK(recvRet, -1, "recv");
memset(salt, 0, sizeof(salt));
memset(pwd, 0, sizeof(pwd));
userID = query(usrname, salt, pwd);
}
//把相应的salt值发送给客户端
flag[0] = 'a';
sendRet = send(sockFd, flag, 1, 0);
sendRet = send(sockFd, salt, sizeof(salt), 0);
ERROR_CHECK(sendRet, -1, "send salt");
//2、用户输入了正确的用户名,接收传过来的加密之后的密码
// 和数据库中保存的进行比对,匹配就返回id,否则返回-1
char inPwd[128] = {0};
recvRet = recv(sockFd, inPwd, sizeof(inPwd), 0);
ERROR_CHECK(recvRet, -1, "recv inPwd");
/* printf("cryptwd = %s\n", pwd); */
/* printf("getpwd = %s\n", inPwd); */
/* printf("%d\n", strcmp(pwd, inPwd)); */
while(strcmp(pwd, inPwd) != 0)
{
//密码输入错误,重新输入
printf("wrong pwd!\n");
memset(inPwd, 0, sizeof(inPwd));
flag[0] = 'p';
sendRet = send(sockFd, flag, 1, 0);
ERROR_CHECK(sendRet, -1, "send ret");
recvRet = recv(sockFd, inPwd, sizeof(inPwd), 0);
ERROR_CHECK(recvRet, -1, "recv inPwd");
}
//用户输入了正确的用户名和密码,返回1
flag[0] = 'a';
send(sockFd, flag, 1, 0);
printf("login successful!\n");
return userID;
}
8:日志表
int logrecord(char *username,char *cmd,int x){
//记录传入的用户和操作
record record_log;
//设置检查命令
char check[20]={"checklog"};
strcpy(record_log.username,username);
//登陆标记为1,加入日志表
if(x==1){
strcpy(record_log.cmd,"clinet join");
insert_log(record_log);
return 0;
}
//退出标记为2,加入日志表
if(x==2){
strcpy(record_log.cmd,"clinet exit");
insert_log(record_log);
return 0;
}
strcpy(record_log.cmd,cmd);
/* //插入日志 */
insert_log(record_log);
//判断是否为查表命令
if(strcmp(record_log.cmd,check)==0){
check_log();
}
return 0;
}
int insert_log(record re_log)
{
MYSQL *conn;
char *server = "localhost";
char *user = "chen";
char *password = "123";
char *database = "37chen";//要访问的数据库名称
char query[200]={0};
sprintf(query,"insert into log ( username, cmd ) values ('%s','%s')",re_log.username,re_log.cmd);
int queryResult;
conn = mysql_init(NULL);
if(!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
{
printf("Error connecting to database: %s\n", mysql_error(conn));
}
else
{
printf("Connected...\n");
}
queryResult = mysql_query(conn, query);
if(queryResult)
{
printf("Error making query:%s\n", mysql_error(conn));
}
else
{
printf("insert success\n");
}
mysql_close(conn);
return 0;
}
int check_log()
{
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
char *server = "localhost";
char *user = "root";
char *password = "";
char *database = "ftp";//要访问的数据库名称
char query[300] = "select id,username,cmd,time from log";
unsigned int queryRet;
//在输出前先打印查询语句
puts(query);
//初始化
conn = mysql_init(NULL);
if(!conn)
{
printf("MySQL init failed\n");
return -1;
}
//连接数据库,看连接是否成功,只有成功才能进行后面的操作
if(!mysql_real_connect(conn, server, user, password, database, 0, NULL, 0))
{
printf("Error connecting to database: %s\n", mysql_error(conn));
return -1;
}
else
{
printf("MySQL Connected...\n");
}
//把SQL语句传递给MySQL
queryRet = mysql_query(conn, query);
if(queryRet)
{
printf("Error making query: %s\n", mysql_error(conn));
}
else
{
//用mysql_num_rows可以得到查询的结果集有几行
//要配合mysql_store_result使用
//第一种判断方式
res = mysql_store_result(conn);
printf("mysql_num_rows = %lu\n", (unsigned long)mysql_num_rows(res));
//第二种判断方式,两种方式不能一起使用
/* res = mysql_use_result(conn); */
row = mysql_fetch_row(res);
if(NULL == row)
{
printf("Don't find any data\n");
}
else
{
do
{
/* printf("num=%d\n",mysql_num_fields(res));//列数 */
//每次for循环打印一整行的内容
for(queryRet = 0; queryRet < mysql_num_fields(res); ++queryRet)
{
printf("%8s ", row[queryRet]);
}
printf("\n");
}while(NULL != (row = mysql_fetch_row(res)));
}
mysql_free_result(res);
}
mysql_close(conn);
return 0;
}
对用户的操作记录在log表中
9:功能演示
文件秒传,比对数据库的md5码值,若相等则不传输,直接返回,将数据库表中cnt引用+1
运用私有协议进行传输
10:客户端部分代码
时间有限未经处理
#define _GNU_SOURCE
#include <fun.h>
#include <sys/sendfile.h>
#include <memory.h>
typedef struct{
int dataLen;
char buf[1000];
}Train_t;
typedef struct {
char type;
long fileSize;
char fileName[20];
}FileInfo, *pFileInfo;
#define READ_DATA_SIZE 1024
#define MD5_SIZE 16
#define MD5_STR_LEN (MD5_SIZE * 2)
#define MAX 1000
int clientCd(int sockFd, char *ss);
int clientCMD(int sockFd);
int clientPwd(int sockFd, char *ss);
int clientLs(int sockFd, char *ss);
int clientUpload(int sockFd, char *fileName);
int Compute_file_md5(const char *file_path, char *value);
int sendFile(int sockFd, const char *fileName);
int CMDtype(const char *ss, char *cmd, char *arg);
typedef struct
{
unsigned int count[2];
unsigned int state[4];
unsigned char buffer[64];
} MD5_CTX;
#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n)))
#define FF(a,b,c,d,x,s,ac) \
{ \
a += F(b,c,d) + x + ac; \
a = ROTATE_LEFT(a,s); \
a += b; \
}
#define GG(a,b,c,d,x,s,ac) \
{ \
a += G(b,c,d) + x + ac; \
a = ROTATE_LEFT(a,s); \
a += b; \
}
#define HH(a,b,c,d,x,s,ac) \
{ \
a += H(b,c,d) + x + ac; \
a = ROTATE_LEFT(a,s); \
a += b; \
}
#define II(a,b,c,d,x,s,ac) \
{ \
a += I(b,c,d) + x + ac; \
a = ROTATE_LEFT(a,s); \
a += b; \
}
void MD5Init(MD5_CTX *context);
void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen);
void MD5Final(MD5_CTX *context, unsigned char digest[16]);
void MD5Transform(unsigned int state[4], unsigned char block[64]);
void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len);
void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len);
//int recvCycle(int sockFd, void *buf, int totalLen);
int clientLogin(int sockFd)
{
//1、提示用户输入用户名,把用户名发送给服务器,
// 根据服务器的反馈如果输入了正确的用户名,接收相应的salt
// 如果输入的是非法用户名,重新输入
char username[20] = {0};
char flag[2] = {0};
char salt[9] = {0}; //服务器发来的用户对应的salt值
char *inputPwd; //用户输入的密码
char *chngPwd; //根据salt转换后的密码
char sendPwd[128] = {0};
int sendRet = 0;
int recvRet = 0;
printf("please input username: ");
scanf("%s", username); //输入用户名
sendRet = sendRet = send(sockFd, username, strlen(username), 0);
recvRet = recv(sockFd, flag, 1, 0); //接收服务器的回馈值
while(flag[0] == 'u')
{
//输入正确的用户名
printf("illegal username!\n");
memset(username, 0, sizeof(username));
printf("please input correct username: ");
scanf("%s", username); //输入用户名
sendRet = sendRet = send(sockFd, username, strlen(username), 0);
recvRet = recv(sockFd, flag, 1, 0); //接收服务器的回馈值
}
//正确接收salt值
do{
memset(salt, 0, sizeof(salt));
recvRet = recv(sockFd, salt, sizeof(salt), 0);
/* printf("recvRet = %d\n", recvRet); */
/* printf("salt = %s\n", salt); */
}while(recvRet != sizeof(salt));
//2、输入密码并根据输入的密码以及salt值,生成对应的密文
// 把生成的密文发送给服务器
// 如果服务器反馈是正确的的密码,登录成功,返回1
// 否则重新输入密码
inputPwd = getpass("please input password: ");
char buf[20] = {0};
sprintf(buf, "$6$%s$", salt);
chngPwd = crypt(inputPwd, buf); //根据salt得到密文
//截断,只获取密文
/* printf("chngPwd = %s\n", chngPwd); */
memcpy(sendPwd, &chngPwd[12], strlen(chngPwd)-12);
sendRet = send(sockFd, sendPwd, strlen(sendPwd), 0);//把密文发给服务器
recvRet = recv(sockFd, flag, 1, 0);
while(flag[0] == 'p'){
//密码错误,重新输入密码
printf("wrong password!\n");
inputPwd = getpass("please input correct password: ");
char buf[20] = {0};
sprintf(buf, "$6$%s$", salt);
chngPwd = crypt(inputPwd, buf); //根据salt得到密文
memset(sendPwd, 0, sizeof(sendPwd));
memcpy(sendPwd, &chngPwd[12], strlen(chngPwd)-12);
sendRet = send(sockFd, sendPwd, strlen(sendPwd), 0);//把密文发给服务器
memset(flag, 0, 2);
recvRet = recv(sockFd, flag, 1, 0);
}
if(flag[0] == 'a'){
printf("login successful!\n");
}
return 1;
}
unsigned char PADDING[] =
{
0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
void MD5Init(MD5_CTX *context)
{
context->count[0] = 0;
context->count[1] = 0;
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
}
void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen)
{
unsigned int i = 0;
unsigned int index = 0;
unsigned int partlen = 0;
index = (context->count[0] >> 3) & 0x3F;
partlen = 64 - index;
context->count[0] += inputlen << 3;
if(context->count[0] < (inputlen << 3))
context->count[1]++;
context->count[1] += inputlen >> 29;
if(inputlen >= partlen)
{
memcpy(&context->buffer[index], input,partlen);
MD5Transform(context->state, context->buffer);
for(i = partlen; i+64 <= inputlen; i+=64)
MD5Transform(context->state, &input[i]);
index = 0;
}
else
{
i = 0;
}
memcpy(&context->buffer[index], &input[i], inputlen-i);
}
void MD5Final(MD5_CTX *context, unsigned char digest[16])
{
unsigned int index = 0,padlen = 0;
unsigned char bits[8];
index = (context->count[0] >> 3) & 0x3F;
padlen = (index < 56)?(56-index):(120-index);
MD5Encode(bits, context->count, 8);
MD5Update(context, PADDING, padlen);
MD5Update(context, bits, 8);
MD5Encode(digest, context->state, 16);
}
void MD5Encode(unsigned char *output,unsigned int *input,unsigned int len)
{
unsigned int i = 0;
unsigned int j = 0;
while(j < len)
{
output[j] = input[i] & 0xFF;
output[j+1] = (input[i] >> 8) & 0xFF;
output[j+2] = (input[i] >> 16) & 0xFF;
output[j+3] = (input[i] >> 24) & 0xFF;
i++;
j += 4;
}
}
void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len)
{
unsigned int i = 0;
unsigned int j = 0;
while(j < len)
{
output[i] = (input[j]) |
(input[j+1] << 8) |
(input[j+2] << 16) |
(input[j+3] << 24);
i++;
j += 4;
}
}
void MD5Transform(unsigned int state[4], unsigned char block[64])
{
unsigned int a = state[0];
unsigned int b = state[1];
unsigned int c = state[2];
unsigned int d = state[3];
unsigned int x[64];
MD5Decode(x,block,64);
FF(a, b, c, d, x[ 0 ], 7, 0xd76aa478); /* 1 */
FF(d, a, b, c, x[ 1 ], 12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, x[ 2 ], 17, 0x242070db); /* 3 */
FF(b, c, d, a, x[ 3 ], 22, 0xc1bdceee); /* 4 */
FF(a, b, c, d, x[ 4 ], 7, 0xf57c0faf); /* 5 */
FF(d, a, b, c, x[ 5 ], 12, 0x4787c62a); /* 6 */
FF(c, d, a, b, x[ 6 ], 17, 0xa8304613); /* 7 */
FF(b, c, d, a, x[ 7 ], 22, 0xfd469501); /* 8 */
FF(a, b, c, d, x[ 8 ], 7, 0x698098d8); /* 9 */
FF(d, a, b, c, x[ 9 ], 12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */
FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */
/* Round 2 */
GG(a, b, c, d, x[ 1 ], 5, 0xf61e2562); /* 17 */
GG(d, a, b, c, x[ 6 ], 9, 0xc040b340); /* 18 */
GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
GG(b, c, d, a, x[ 0 ], 20, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, x[ 5 ], 5, 0xd62f105d); /* 21 */
GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */
GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
GG(b, c, d, a, x[ 4 ], 20, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, x[ 9 ], 5, 0x21e1cde6); /* 25 */
GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */
GG(c, d, a, b, x[ 3 ], 14, 0xf4d50d87); /* 27 */
GG(b, c, d, a, x[ 8 ], 20, 0x455a14ed); /* 28 */
GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */
GG(d, a, b, c, x[ 2 ], 9, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, x[ 7 ], 14, 0x676f02d9); /* 31 */
GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH(a, b, c, d, x[ 5 ], 4, 0xfffa3942); /* 33 */
HH(d, a, b, c, x[ 8 ], 11, 0x8771f681); /* 34 */
HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
HH(a, b, c, d, x[ 1 ], 4, 0xa4beea44); /* 37 */
HH(d, a, b, c, x[ 4 ], 11, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, x[ 7 ], 16, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */
HH(d, a, b, c, x[ 0 ], 11, 0xeaa127fa); /* 42 */
HH(c, d, a, b, x[ 3 ], 16, 0xd4ef3085); /* 43 */
HH(b, c, d, a, x[ 6 ], 23, 0x4881d05); /* 44 */
HH(a, b, c, d, x[ 9 ], 4, 0xd9d4d039); /* 45 */
HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, x[ 2 ], 23, 0xc4ac5665); /* 48 */
/* Round 4 */
II(a, b, c, d, x[ 0 ], 6, 0xf4292244); /* 49 */
II(d, a, b, c, x[ 7 ], 10, 0x432aff97); /* 50 */
II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
II(b, c, d, a, x[ 5 ], 21, 0xfc93a039); /* 52 */
II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */
II(d, a, b, c, x[ 3 ], 10, 0x8f0ccc92); /* 54 */
II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
II(b, c, d, a, x[ 1 ], 21, 0x85845dd1); /* 56 */
II(a, b, c, d, x[ 8 ], 6, 0x6fa87e4f); /* 57 */
II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, x[ 6 ], 15, 0xa3014314); /* 59 */
II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
II(a, b, c, d, x[ 4 ], 6, 0xf7537e82); /* 61 */
II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
II(c, d, a, b, x[ 2 ], 15, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, x[ 9 ], 21, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
int sendFilename(int sfd,char* filename)
{
Train_t train;
train.dataLen=strlen(filename);//传输文件名的长度
strcpy(train.buf,filename);//传输的文件名的具体内容
int ret = send(sfd,&train,4+train.dataLen,0);
printf("ret = %d\n",ret);
int fd =open(filename,O_RDWR);
struct stat state;
fstat(fd,&state);
off_t totalsize;
send(sfd,&state.st_size,sizeof(off_t),0);
//发送文件md5码
sendfile(sfd,fd,0,state.st_size);//发送文件传输
}
int main(int argc, char** argv)
{
int sfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serAddr;
memset(&serAddr, 0, sizeof(serAddr));
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = inet_addr(argv[1]);
serAddr.sin_port = htons(atoi(argv[2]));
int ret = 0;
ret = connect(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr));
clientLogin(sfd);
char buf[1000] = {0};
char brr[20]="puts file";
//printf("指定的命令为%s\n",brr);
//write(sfd,buf,sizeof(brr));
send(sfd,brr,sizeof(brr),0);
//recv(sfd,buf,sizeof(buf),0);
//printf("命令为 %s\n",brr);
char *ptmp = strchr(brr, ' '); //走到中间的空格停下来
char data[20] = {0};
Train_t train;
train.dataLen=strlen(data);//
strcpy(data,ptmp+1);
printf("发送的文件名是%s\n",data);
sendFilename(sfd,data);
while(1);
//send(sfd,brr,strlen(brr),0);
//memset(brr,0,sizeof(brr));
//recv(sfd,brr,sizeof(brr),0);
//printf("rm arr的结果为%s\n",brr);
//sleep(1);
// char arr[100]="ls";
//printf("指定的命令为%s\n",arr);
// send(sfd,arr,strlen(arr),0);
int dataLen = 0;
//memset(arr,0,sizeof(arr));
// recv(sfd,arr,sizeof(arr),0);
// printf("%s\n",arr);
// while(1);
ret = recv(sfd, &dataLen, 4, 0);
recv(sfd, buf, dataLen, 0);
int fd = open(buf, O_RDWR|O_CREAT, 0666);
off_t fileSize = 0;
off_t recvLen = 0;
recv(sfd, &dataLen, 4, 0);
recv(sfd, &fileSize, dataLen, 0);
printf("fileSize = %ld\n", fileSize);
float rate = 0;
struct timeval begin, end;
gettimeofday(&begin, NULL);
int sfd1[2];
pipe(sfd1);
int recvLen1 = 0;
while(recvLen1 < fileSize)
{
int ret = splice(sfd, 0, sfd1[1], 0, 128, 0);
ret = splice(sfd1[0], 0, fd, 0, ret, 0);
recvLen1 += ret;
}
/* while(1){ */
/* memset(buf, 0, sizeof(buf)); */
/* ret = recv(sfd, &dataLen, 4, 0); */
/* if(0 == dataLen){ */
/* break; */
/* } */
/* ret = recv(sfd, buf, dataLen, MSG_WAITALL); */
/* ret = recvCycle(sfd, buf, dataLen); */
//计算此时总接收的字节数
/* recvLen += ret; */
/* rate = (float)recvLen / fileSize *100; */
/* printf("rate = %5.2f %%\r", rate); */
/* fflush(stdout); */
/* if(1000 != ret){ */
/* printf("ret = %d\n", ret); */
/* } */
/* write(fd, buf, ret); */
/* } */
/* printf("\n"); */
/* gettimeofday(&end, NULL); */
/* printf("cost time = %ld\n", (end.tv_sec - begin.tv_sec)*1000000 + (end.tv_usec - begin.tv_usec)); */
close(sfd);
return 0;
}
int clientUpload(int sockFd, char *fileName){
//告诉服务器要自己要上传文件了
char buf[20] = "puts file";
//memcpy(buf, "upload", 6);
char flag;
int ret;
//int sd=send(sockFd, buf, 9, 0);
//printf("sned 客户端发送的%d\n",sd);
//1、生成文件对应的MD5码,为了提高效率,根据文件的前500B来生成
char md5str[MD5_STR_LEN + 1] = {0};
Train_t train; //传输用的小火车
memset(&train, 0, sizeof(train));
ret = Compute_file_md5(fileName, md5str);
if(0 == ret){
printf("[file - %s] md5 value:\n", fileName);
printf("%s\n", md5str);
}
//2、把文件的信息发送给服务器
// (1)发送文件名
/*
train.dataLen = strlen(fileName);
memcpy(train.buf, fileName, train.dataLen);
printf("%s\n",train.buf);
ret = send(sockFd, &train, 4+train.dataLen, 0);
printf("发送文件名是%s,len = %d\n",fileName,ret);
*/
//send(sockFd,fileName,strlen(fileName),0);
// (2)发送文件MD5码
/*
memset(&train, 0, sizeof(train));
train.dataLen = strlen(md5str);
memcpy(train.buf, md5str, train.dataLen);
int ret1 = send(sockFd, &train, 4+train.dataLen, 0);
printf("发送的文件md5码长度%d\n",ret1);
// (3)发送文件大小
//获取文件的大小
struct stat sstat;
stat(fileName, &sstat);
int fileSize = (int)sstat.st_size;
//发送文件的大小
memset(&train, 0, sizeof(train));
train.dataLen = sizeof(fileSize);
memcpy(train.buf, &fileSize, train.dataLen);
send(sockFd, &train, 4+train.dataLen, 0);
*/
//3、发送文件,接收反馈,看服务器是否成功接收
sendFile(sockFd, fileName);
recv(sockFd, &flag, 1, 0);
while(flag == 'u'){
//服务器接收失败,重新传输
printf("send field!\n");
return -1;
}
if(flag == 'a'){
//发送成功
printf("send successful!\n");
return 1;
}
return 1;
}
int sendFile(int clientFd, const char *fileName)
{
//采用“零拷贝”的方式根据请求发送不定长的文件
//打开文件,读取并发送
int fd = open(fileName, O_RDWR);
//获取文件大小
struct stat fileInfo;
fstat(fd, &fileInfo);
//使用零拷贝接口splice
//需要借助管道
int fdPipe[2];
pipe(fdPipe);
int sendRet = 0;
int sendSize = 0;
while(sendSize < fileInfo.st_size){
//循环从文件中读出,然后通过管道发送个客户端
sendRet = splice(fd, 0, fdPipe[1], 0, MAX, 0);
sendRet = splice(fdPipe[0], 0, clientFd, 0, MAX, 0);
if(-1 == sendRet){
//连接断了,直接返回!
printf("client exit!\n");
/* break; */
return -1;
}
sendSize += sendRet;
}
close(fd);
close(fdPipe[0]);
close(fdPipe[1]);
return 1;
}
int Compute_file_md5(const char *file_path, char *md5_str)
{
int i;
int fd;
int ret;
unsigned char data[READ_DATA_SIZE];
unsigned char md5_value[MD5_SIZE];
MD5_CTX md5;
fd = open(file_path, O_RDONLY);
if (-1 == fd)
{
perror("open");
return -1;
}
// init md5
MD5Init(&md5);
while (1)
{
ret = read(fd, data, READ_DATA_SIZE);
if (-1 == ret)
{
perror("read");
return -1;
}
MD5Update(&md5, data, ret);
if (0 == ret || ret < READ_DATA_SIZE)
{
break;
}
}
close(fd);
MD5Final(&md5, md5_value);
for(i = 0; i < MD5_SIZE; i++)
{
snprintf(md5_str + i*2, 2+1, "%02x", md5_value[i]);
}
md5_str[MD5_STR_LEN] = '\0'; // add end
return 0;
}