C语言线程池实现并行下载上传,匹配百度网盘功能

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值