C++项目整理:网盘项目

您所在的位置:网站首页 cocos2dx教程百度网盘 C++项目整理:网盘项目

C++项目整理:网盘项目

2023-08-20 23:48| 来源: 网络整理| 查看: 265

一、项目简介:

     本次项目以网盘为题目,设计一个基于C/C++语言开发的网盘系统。由Vitual Studio打造的一个网盘系统项目,后台数据库使用MySQL数据库开发而成。结合网上诸多的云网盘软件为设计为基础,自己设计的网盘系统。该系统可以注册用户,已有的用户可以直接登录进入网盘主界面,上传文件以及下载已有文件或者删除,可以对文件的获取链接请求,并且分享文件以及链接。

二、项目主体:

1、数据库设计:

我创建了网盘数据库,在该数据库中存在四个表用以存储数据:有文件表存储文件信息(file),用户表存储用户信息(user),用户文件表用于储存一个用户的文件(user_file),文件链接表用于储存文件及其文件链接(share_file)。

表1 file表示例

字段名

说明

类型

长度

可否为空

主键

f_id

文件id

int

4

主键

F_name

文件名

varchar

20

 

F_uploadtime

上传时间

varchar

20

 

F_size

文件大小

varchar

100

 

F_path

文件路径

Varchar

100

 

F_count

文件应用数

Int

4

 

F_MD5

文件MD5值

Int

4

 

 

表2 user表示例

字段名

说明

类型

长度

可否为空

主键

U_id

用户编号

int

4

主键

U_name

用户名

varchar

20

 

U_password

用户密码

varchar

20

 

 

表3 user_file表示例

字段名

说明

类型

长度

可否为空

主键

U_id

用户编号

int

4

主键

F_id

文件编号

varchar

20

主键

 

表4 share_file表示例

字段名

说明

类型

长度

可否为空

主键

U_id

用户编号

int

4

主键

F_id

文件编号

varchar

20

主键

S_link

文件链接

varchar

20

 

2、功能实现:

(1)用户注册:

       由界面提交注册信息,包括用户id,姓名,密码,向服务器端发送注册信息;服务器端接受处理,尝试向数据库插入注册信息,根据约束条件,如果成功插入,返回注册成功,创建用户对应的路径;否则注册失败。返回注册结果后,再在客户端接受并处理,弹窗显示注册结果。

void TCPKernel::RegisterRq(char *szbuf,SOCKET sock) { //注册请求包 STRU_REGISTER_RQ *psrr = (STRU_REGISTER_RQ*)szbuf; //将用户信息写入到数据库里 char szsql[_DEF_SQLLEN_] = {0}; STRU_REGISTER_RS srr; srr.m_ntype = _DEF_PRTOCOL_REGISTER_RS; srr.m_szResult = _register_fail; sprintf_s(szsql,"insert into user values(%lld,'%s','%s')",psrr->m_userid, psrr->m_szName,psrr->m_szPassword); if(m_sql.UpdateMySql(szsql)) { srr.m_szResult = _register_success; char szPath[MAX_PATH] = {0}; sprintf_s(szPath,MAX_PATH,"%s%lld",m_szSystemPath,psrr->m_userid); CreateDirectoryA(szPath,NULL); //int n = GetLastError(); } m_pNet->SendData(sock,(char*)&srr,sizeof(srr)); }

(2)用户登录:

       客户端界面提交登录用户id,密码等信息,向服务器端接收并处理,用户id查表,获取密码,如果能找到密码,并且密码一致,那么登录成功,否则登录失败。再返回客户端后,接收并处理登录结果,登录成功向窗口发送消息,否则弹窗提示登录失败。然后当用户登录后我设计了创建主界面窗口。

//登录请求 void TCPKernel::LoginRq(char *szbuf,SOCKET sock) { //登录请求包 STRU_LOGIN_RQ *pslr = (STRU_LOGIN_RQ*)szbuf; char szsql[_DEF_SQLLEN_] = {0}; list lststr; STRU_LOGIN_RS slr; slr.m_ntype = _DEF_PRTOCOL_LOGIN_RS; slr.m_szResult = _login_fail; sprintf_s(szsql,"select u_password from user where u_id =%lld;",pslr->m_userid); //去数据库里面查询用户对应的密码,放入链表中 m_sql.SelectMySql(szsql,1,lststr); if(lststr.size() >0) { //从链表中取出密码 string strPassword = lststr.front(); lststr.pop_front(); //比较密码是否相同 if(0 == strcmp(pslr->m_szPassword,strPassword.c_str())) { slr.m_szResult = _login_success; } } m_pNet->SendData(sock,(char*)&slr,sizeof(slr)); }

(3)获取用户文件列表:

        在客户端主界面窗口创建时,在初始化函数中向服务器提交请求获取文件列表,用户信息包括用户id,然后向服务器发送获取列表请求,服务器接收并处理,根据用户id 联表查询该用户上的所有上传过的文件,将文件的名字按照每50个一组的形式写入获取文件列表回复中,发送给客户端。客户端返回获取列表回复后发送窗口消息,消息有文件信息数组,文件个数。最后在客户端中处理窗口消息,循环向主界面窗口插入已有文件信息。

//获取用户文件信息列表 void TCPKernel::GetFileListRq(char *szbuf,SOCKET sock) { STRU_GETFILELIST_RQ *psgr = (STRU_GETFILELIST_RQ*)szbuf; char szsql[_DEF_SQLLEN_] = {0}; list lststr; STRU_GETFILELIST_RS sgr; sgr.m_ntype = _DEF_PRTOCOL_GETFILELIST_RS; //根据userid 从数据库中取出file 信息 sprintf_s(szsql,"select f_name,f_uploadtime,f_size from user \ inner join user_file on user.u_id = user_file.u_id \ inner join file on user_file.f_id = file.f_id \ where user.u_id = %lld;",psgr->m_userid); m_sql.SelectMySql(szsql,3,lststr); int i = 0; //遍历链表 while(lststr.size() >0) { string strFileName =lststr.front(); lststr.pop_front(); string strFileUpLoadTime =lststr.front(); lststr.pop_front(); string strFileSize =lststr.front(); lststr.pop_front(); sgr.m_aryFile[i].m_FileSize = _atoi64(strFileSize.c_str()); strcpy_s(sgr.m_aryFile[i].m_szFileName,_DEF_SIZE,strFileName.c_str()); strcpy_s(sgr.m_aryFile[i].m_szUpLoadTime,_DEF_SIZE,strFileUpLoadTime.c_str()); i++; if(lststr.size() ==0 || i == _DEF_FILENUM) { //发送回复 sgr.m_nFileNum = i; m_pNet->SendData(sock,(char*)&sgr,sizeof(sgr)); i = 0; ZeroMemory(sgr.m_aryFile,sizeof(sgr.m_aryFile)); } } }

(4)上传文件:

        这是实现网盘的最重要的一步,步骤如下:首先,在客户端上点击上传按钮准备上传文件,弹窗上选择要上传的文件,确认后,获取文件的文件名和路径,根据文件生成MD5。然后,向服务器发送上传文件头请求,请求内容包括文件的大小,文件MD5,上传时间,上传位置,同时记录上传文件信息,我用了一个映射存储文件信息和文件,用于后面的查找。然后,向服务器端发送文件头请求,服务器根据文件名和文件MD5,查看该文件是否存在,情况1:如果文件已经存在,那么查看用户id是否对应这个人,如果是的话,则提示文件已经传过,如果不是则秒传文件,(),情况2:如果文件不存在的话就可以正常传文件,根据用户id和文件名,创建文件,更新文件信息表,即数据库插入数据,向文件表中添加映射。然后,返回文件头请求,客户端接收并处理回复,进行,弹窗提示,向窗口插入消息提示文件传送成功。在上传新文件时开启一个线程完成内容的全部发送,线程的目的是为了打开文件读取文件,发送文件块,文件id到服务器,并将当前发送的大小粘贴到表格控件上面。最后,服务器接收文件拿到对应的文件信息结构体,从而拿到文件指针,向文件里面写传说内容。(循环进行),当文件大小与传送字节数一直时关闭文件,完成上传。同时在map中将文件信息节点删除。

//上传文件头请求(文件名,id,路径,md5) void TCPKernel::UpLoadFileHeaderRq(char *szbuf,SOCKET sock) { //上传文件头 STRU_UPLOADFILEHEADER_RQ *psur = (STRU_UPLOADFILEHEADER_RQ*)szbuf; char szsql[_DEF_SQLLEN_] = {0}; list lststr; STRU_UPLOADFILEHEADER_RS sur; sur.m_ntype = _DEF_PRTOCOL_UPLOAD_FILEHEADER_RS; strcpy_s(sur.m_szMD5,_DEF_SIZE,psur->m_szMD5); sur.m_fileid = 0; sprintf_s(szsql,"select file.f_id,u_id,f_count from user_file \ inner join file on user_file.f_id = file.f_id \ where f_name = '%s' and f_MD5 = '%s' ;",psur->m_szFileName,psur->m_szMD5); m_sql.SelectMySql(szsql,3,lststr); if(lststr.size() >0) { string strFileId = lststr.front(); lststr.pop_front(); string strUserId = lststr.front(); lststr.pop_front(); string strFileCount = lststr.front(); lststr.pop_front(); long long userid = _atoi64(strUserId.c_str()); if(psur->m_userid == userid) { //1.查看自己是否传过, sur.m_szResult = _fileheader_uploaded; //1.1如果自己传过,回复 已经上传过了 } else { sur.m_szResult = _fileheader_uploadsuccess; sur.m_fileid = _atoi64(strFileId.c_str()); //2.查看别人传没传过 如果别人传过,秒传成功 long long filecount = _atoi64(strFileCount.c_str()); //引用计数+1 sprintf_s(szsql,"update file set f_count = %lld where f_MD5 = '%s' and f_name = '%s'" ,++filecount,psur->m_szMD5,psur->m_szFileName); m_sql.UpdateMySql(szsql); //将文件与用户做映射 sprintf_s(szsql,"insert into user_file values(%lld,%lld)" ,psur->m_userid,_atoi64(strFileId.c_str())); m_sql.UpdateMySql(szsql); } } else { sur.m_szResult = _fileheader_continueupload; FILE *pFile = NULL; //3.否则 创建文件 回复 可以正常传 char szPath[MAX_PATH] = {0}; sprintf_s(szPath,MAX_PATH,"%s%lld/%s",m_szSystemPath,psur->m_userid,psur->m_szFileName); fopen_s(&pFile,szPath,"wb"); //更新文件信息表 sprintf_s(szsql,"insert into file(f_name,f_uploadtime,f_size,f_path,f_count,f_MD5) values('%s','%s',%lld,'%s',1,'%s')" ,psur->m_szFileName,psur->m_szUpLoadTime,psur->m_FileSize,szPath,psur->m_szMD5); m_sql.UpdateMySql(szsql); //获取文件ID sprintf_s(szsql,"select f_id from file where f_MD5 = '%s'",psur->m_szMD5); m_sql.SelectMySql(szsql,1,lststr); if(lststr.size() >0) { string strFileid = lststr.front(); lststr.pop_front(); sur.m_fileid = _atoi64(strFileid.c_str()); //更新用户映射文件表 sprintf_s(szsql,"insert into user_file values(%lld,%lld)" ,psur->m_userid,_atoi64(strFileid.c_str())); m_sql.UpdateMySql(szsql); // 将文件信息保存 (fileid pFile filesize userid) STRU_FILEINFO* pInfo = new STRU_FILEINFO; pInfo->m_userid = psur->m_userid; pInfo->m_filesize = psur->m_FileSize; pInfo->m_pFile = pFile; pInfo->m_uploadFilePos = 0; pInfo->m_fileid = sur.m_fileid; strcpy_s(pInfo->m_szMD5,_DEF_SIZE,psur->m_szMD5); m_mapFileidToFileInfo[sur.m_fileid] = pInfo; } } m_pNet->SendData(sock,(char*)&sur,sizeof(sur)); } //上传文件内容请求 void TCPKernel::UpLoadFileContentRq(char* szbuf , SOCKET sock) { STRU_UPLOADFILECONTENT_RQ* psur = (STRU_UPLOADFILECONTENT_RQ*)szbuf; STRU_FILEINFO *pInfo = m_mapFileidToFileInfo[psur->m_fileid]; if(pInfo == NULL) { return; } //写入文件内容 int nRealWriteNum = fwrite(psur->m_szContent,sizeof(char),psur->m_nLen,pInfo->m_pFile); if(nRealWriteNum > 0) { pInfo->m_uploadFilePos += nRealWriteNum; if(pInfo->m_uploadFilePos == pInfo->m_filesize) { fclose(pInfo->m_pFile); STRU_UPLOADFILECONTENT_RS sur; sur.m_ntype = _DEF_PRTOCOL_FILECONTENT_RS; sur.m_fileid = psur->m_fileid; sur.m_szResult = 1; m_pNet->SendData(sock,(char*)&sur,sizeof(sur)); auto ite = m_mapFileidToFileInfo.begin(); while(ite != m_mapFileidToFileInfo.end()) { if(ite->first == psur->m_fileid) { delete pInfo; pInfo = NULL; ite = m_mapFileidToFileInfo.erase(ite); break; } ++ite; } } } }

(5)下载文件:

       客户端在界面里面点击下载按钮,下载选中项。从表格里面获取文件信息,然后弹窗,选择保存路径,保存map,发送下载请求,服务器端接收处理下载请求,根据文件名和用户id,查表得到文件信息,查不到,返回下载失败,查到文件信息,存储在结构体FileInfo,存储到map。开启发送文件块线程,循环读取文件内容,发送文件块(ileid , 文件内容)向服务器发送下载请求,内容userid ,文件名客户端接收处理处理请求回复,创建map,打开文件指针处理下载文件块根据ileid ,在map中找到文件信息,然后拿到文件指针,向文件中写入当文件大小和文件下载字节数相等时,下载完毕,删除map节点提示下载完成,发送文件回复。

void TCPKernel::DownloadRq(char* szbuf , SOCKET sock) { //拆包 STRU_DOWNLOAD_RQ * psdr = (STRU_DOWNLOAD_RQ*)szbuf; //定义RS结构体 STRU_DOWNLOAD_RS sdr; sdr.m_szResult = _file_downloadrq_failed; sdr.m_ntype = _DEF_PRTOCOL_DOWNLOAD_CONTINUEFILEHEADER_RS; //查表 char szsql[_DEF_SQLLEN_] = {0}; list lststr; sprintf_s(szsql,"select file.f_id ,file.f_MD5,file.f_path,file.f_size from file\ inner join user_file on file.f_id = user_file.f_id\ and user_file.u_id = %lld and file.f_name = '%s';",psdr->m_userid,psdr->m_szFileName); DownloadFileInfo * pInfo = 0; if(m_sql.SelectMySql(szsql , 4 ,lststr)) { if(lststr.size() > 0) { //查到了 sdr.m_szResult = _file_downloadrq_success; string strField , strMD5 , strPath , strSize; strField = lststr.front(); lststr.pop_front(); strMD5 = lststr.front(); lststr.pop_front(); strPath = lststr.front(); lststr.pop_front(); strSize = lststr.front(); lststr.pop_front(); strcpy_s(sdr.m_szMD5 , _DEF_SIZE , strMD5.c_str()); sdr.m_fileid = _atoi64(strField.c_str()); strcpy_s(sdr.m_szFileName , _DEF_SIZE , psdr->m_szFileName); //存储到mapstrFile = lststr.front(); //存储到map pInfo = new DownloadFileInfo; pInfo->m_fileid = sdr.m_fileid; pInfo->m_filesize = _atoi64(strSize.c_str()); //打开文件: FILE * pFile = NULL; fopen_s(&pFile, strPath.c_str() , "rb"); pInfo->m_pFile = pFile; pInfo->m_sock = sock; pInfo->m_FilePos = 0; pInfo->m_userid = psdr->m_userid; m_mapFileIDToDownLoadFileInfo[sdr.m_fileid] = pInfo; // todo FileInfo map } } m_pNet->SendData(sock , (char*)&sdr , sizeof(sdr)); //开启发送文件块线程 if(sdr.m_szResult = _file_downloadrq_success) { //todo _beginthreadex(0,0,&TCPKernel::ThreadPro , (void*)pInfo,0,0); } }

(6)删除文件:

      删除较为两端删除 : 客户端从列表删除信息 , 服务器用户文件删除列表里的层次: 第一, 引用计数不到0 , 删除映射关系 第二, 引用计数为0 , 删除表中的文件信息. 同时, 你要考虑要不要删除磁盘里面的文件客户端发起, 文件删除, 删除控件上选中的文件内容:用户id , 文件名发送删除请求 服务器服务器处理 ,根据用户id , 文件名, 查表找文件找不到 删除失败找到了, 先删除映射关系 , 然后将文件引用计数-1引用计数-1之后, 如果为0, 删除信息从文件表里面删除成功写删除回复, 内容 结果, 文件名返回回复,客户端处理根据结果, 弹窗提示删除成功, 根据文件名, 从表格里面删除对应项。

void TCPKernel::DeleteFileRq(char *szbuf,SOCKET sock) { STRU_DELETEFILE_RQ *psdr = (STRU_DELETEFILE_RQ *)szbuf; STRU_DELETEFILE_RS sdr; sdr.m_ntype = _DEF_PROTOCOL_DELETEFILE_RS; sdr.m_szResult = deletefile_failed; strcpy_s(sdr.m_szFileName,_DEF_SIZE,psdr->m_szFileName); //查找 找到userid 的对应信息 char szsql[_DEF_SQLLEN_] = {0}; list lststr; sprintf_s(szsql,"select file.f_id from file inner join user_file on user_file.f_id = file.f_id and file_file.u_id = %lld and file.f_name = '%s'",psdr->m_userid,psdr->m_szFileName); if(m_sql.SelectMySql(szsql,1,lststr)) { if(lststr.size() > 0) { sdr.m_szResult = deletefile_success; //删除文件映射 string strFileID = lststr.front(); lststr.pop_front(); long long IFileID = _atoi64(strFileID.c_str()); ZeroMemory(szsql,sizeof(szsql)); sprintf_s(szsql,"delete from user_file where u_id = %lld and f_id = %lld;",psdr->m_userid,IFileID); m_sql.UpdateMySql(szsql); //引用计数-1 ZeroMemory(szsql,sizeof(szsql)); listlstFile; sprintf_s(szsql,"select f_count from file where f_id = %lld;",IFileID); m_sql.UpdateMySql(szsql); //是否归0,删除文件信息 long long nCount = _atoi64(lstFile.front().c_str()); lstFile.pop_front(); if(nCount == 0) { ZeroMemory(szsql,sizeof(szsql)); sprintf_s(szsql,"delete from file where f_id = %lld;",IFileID); m_sql.UpdateMySql(szsql); } } } }

(7)续传文件:

         在发生异常的情况下, 客户端退出, 重新进入.。从配置文件里面读取正在上传的文件信息( 文件名, 路径, 大小, 上传时间) , 存在map。 开始上传把信息写到配置文件里, 当下载完成, 就把这一条信息从配置里删除。用户点击续传, 发送续传请求(文件名,md5 , userid).发送续传请求,服务器端收到续传请求, 根据文件名,md5 ,userid , 找到pFile ,关闭文件指针map续传是文件传到自己的路径下.找到对应没传完的文件, 读取当前文件的字节数, 返回给客户端( MD5 , 文件名 , fileid , 已接受字节数)返回续传回复根据MD5 , map中, 找FileInfo , 写入文件位置, 然后开启线程打开文件, 跳转到已接受字节位置, 开始发送文件块( fileid , 文件内容)发送文件块 后面同上传处理.

void TCPKernel::UploadContinueRq(char *szbuf,SOCKET sock) { STRU_UPLOAD_CONTINUE_RQ *psur = (STRU_UPLOAD_CONTINUE_RQ *)szbuf; psur->m_szFileName; psur->m_szMD5; psur->m_userid; //根据成员,拿到文件上传字节数,文件id //如果服务器异常,查表: auto ite = m_mapFileidToFileInfo.begin(); while( ite != m_mapFileidToFileInfo.end() ) { if(ite->second->m_userid == psur->m_userid && strcmp(psur->m_szMD5,ite->second->m_szMD5) == 0) { fclose( ite->second->m_pFile); break; } ite++; } if(ite == m_mapFileidToFileInfo.end() ) return;//没找到 //如果有,根据用户路径,拿文件,得size返回结果 //续传一定是在用户得路径下面传 char szPath[MAX_PATH] = {0}; sprintf_s(szPath,MAX_PATH,"%s%lld%s",m_szSystemPath,psur->m_userid,psur->m_szFileName); FILE * pFile = 0; fopen_s(&pFile,szPath,"rb"); _fseeki64(pFile,0,SEEK_END); long long length = _ftelli64(pFile); //读取文件字节数 fclose(pFile); fopen_s(&pFile,szPath,"ab"); _fseeki64(pFile,length,SEEK_SET); //到文件尾 ite->second->m_pFile = pFile; ite->second->m_filesize = length; //回复: STRU_UPLOAD_CONTINUE_RS sur; sur.m_ntype = _DEF_PRTOCOL_UPLOAD_CONTINUEFILEHEADER_RS; sur.m_fileid = ite->first; sur.m_nPos = length; strcpy_s(sur.m_szFileName,_DEF_SIZE,psur->m_szFileName); strcpy_s(sur.m_szMD5,_DEF_SIZE,psur->m_szMD5); m_pNet->SendData(sock , (char *)&sur,sizeof(sur)); }

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3