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;//存储客户端的fdchar 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.创建epollint 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++;elsebreak;}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->ItemNamestrcpy(p_confitem->ItemContent,ptmp+1);                    //等号右侧的拷贝到p_confitem->ItemContentRtrim(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,用户idchar 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语句传递给MySQLqueryRet = 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))){//如果找到了就,直接返回1printf("客户端有该文件\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 格式化为
idididsalt$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,否则返回-1char 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");}//用户输入了正确的用户名和密码,返回1flag[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语句传递给MySQLqueryRet = 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 md5MD5Init(&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 endreturn 0;
}

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

  1. 记录一次 用服务器下载文件,并上传到百度网盘的实践

    最近学会使用DepotDownloader下载旧版游戏,担心有一天steam不给下载了,所以就想他们都下载下来,然后保存到网盘上,然后分享出来. 我所下载的游戏叫<泰拉瑞亚>最新版有1.3 ...

  2. win10系统如何删除右键菜单中的上传到百度网盘

    win10系统如何删除右键菜单中的上传到百度网盘 https://jingyan.baidu.com/article/e4d08ffde65bb44fd3f60d45.html

  3. eb8000软件怎样上传_百度网盘如何免费上传超过4G的文件?BitComet来帮你!

    百度网盘是现在非常常用的云存储工具,但它每次上传的文件最大就是4G,上传一些文件.电影等,超过这个大小就要开通会员才行,今天要教你的就是怎样免费上传超过4G的大文件. 1.首先下载BitComet(比 ...

  4. 把自己的文件上传到百度网盘,成为公开资源的教程

    1.上传一个文件 打开百度网盘,上传一个文件, 如图: 2.分享文件 右键点击"分享", 如图: 3.上传文件 设置好分享方式, 如图: 4.创建链接 创建一个链接, 如图: 点击 ...

  5. Xcode 11的问题及 Xcode 11 beta 1和beta 2 版下载链接, 官方下载后上传到百度网盘的.

    Xode 11 beta 1和beta 2 版下载 链接:https://pan.baidu.com/s/1ZqWoDaIgUGzGdupPqHJIvQ 密码:q9gy 链接:https://pan. ...

  6. 多人上传文件公共网盘_上传网盘的信息也会泄露?个人隐私照片被多人传看,太过分了...

    原标题:上传网盘的信息也会泄露?个人隐私照片被多人传看,太过分了 关于百度网盘泄露隐私的事件已经不是第一次了,2017年就曾发生过一次非常严重的隐私泄露事件.在那次泄露的事件中,大量私人信息,包括身份 ...

  7. 阿里云网盘和百度网盘功能体验上谁更胜一筹呢?

    阿里云网盘和百度网盘功能体验上谁更胜一筹呢? 由阿里巴巴推出的阿里云网盘即将上线,相信这对于百度网盘来说将是一个强劲的对手,会流失许多的用户.那么阿里云网盘和百度网盘哪个好?这两个网盘在功能和体验上谁 ...

  8. 阿里云网盘内测_阿里云网盘内测,下载10M/s;百度网盘...?

    近日,有好友说阿里也开始做网盘了,这就激起了我的好奇与兴奋,想着目前百度网盘一家独大,下载速度更是喜人,阿里云网盘一定会给大家带来惊喜.据悉,阿里云网盘普通用户有1000GB(1T)空间,会员有6T的 ...

  9. Elasticsearch-7.0和Logstash-7.0和Kibaa-7.0的下载以及安装(百度网盘)

    一.前言 这几天好不容易把ES-6.2.4给研究完,结果在4月10号那天,ES-7.0发布了,我的天.没办法,只能再把本地的ELK都升级一下.这篇博客主要是分享一下百度网盘的下载地址,因为官方的网络下 ...

最新文章

  1. Chemistry.AI | 基于非线性激活的多层感知器预测分子特性
  2. OSChina 周四乱弹 ——震惊!程序员的时间都用来干这个!
  3. BigDecimal去除末尾多余的0
  4. 脚本调用后台代码 asp.net ajax
  5. 25个经典的Spring面试问答
  6. AI in RTC 创新挑战赛,决赛打响
  7. 编程开发之--Oracle数据库--存储过程在out参数中使用光标(3)
  8. .net开发工程师是做什么的_.NET能开发出什么样的APP?盘点那些通过Smobiler开发的移动应用...
  9. ubuntu sudo apt-get update 失败 解决方法
  10. mybatis缓存查找顺序
  11. 接口没获取到就被使用_使用CompletableFuture时,那些令人头疼的问题
  12. 阿里巴巴全资收购中国网络配送平台饿了么
  13. 使用hue时hive的历史查询记录结果无法下载
  14. 现代管理学 罗珉 第三版
  15. 如何通过AWS VPC Peering云服务,解决公共云的局限性?
  16. 利用python,求解数独
  17. ORA-12569: TNS: 包校验和失败解决方法一例
  18. 出现“你的Windows许可证即将过期”提示
  19. [C++]稀疏矩阵(一维数组描述)
  20. 系统架构设计师-软件水平考试(高级)-论文-架构风格

热门文章

  1. 深度神经网络压缩与加速总结
  2. 图像深度、像素深度和位深度
  3. ckpt下载 deeplabv3_煮酒论英雄:深度学习CV领域最瞩目的成果top46
  4. 怎么翻译截图里面的文字?快来看看截图翻译怎么弄
  5. 机器学习算法(十二):聚类
  6. 服务器后端开发系列——《实战Nginx高性能Web服务器》
  7. 解决锐捷客户端登陆时网卡网关配置有误的方法
  8. 教你如何做抖音视频广告,助你脱颖而出
  9. 乐山农业银行机器人_智能机器人“小新”亮相农业银行
  10. AdVoice广告录音制作软件如何音乐语音混音穿插制作广告