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

一、项目简介:

     本次项目以网盘为题目,设计一个基于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<string> 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<string> 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<string> 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<fleid , Filelnfo>,打开文件指针处理下载文件块根据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<string> 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<string> 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));
			list<string>lstFile;
			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, FileInfo>。 开始上传把信息写到配置文件里, 当下载完成, 就把这一条信息从配置里删除。用户点击续传, 发送续传请求(文件名,md5 , userid).发送续传请求,服务器端收到续传请求, 根据文件名,md5 ,userid , 找到pFile ,关闭文件指针map<fileid , FileInfo>续传是文件传到自己的路径下.找到对应没传完的文件, 读取当前文件的字节数, 返回给客户端( 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));

}

 

文件内包含 Apache C++ 、Standard Library、ASL、Boost、BDE、Cinder、Cxxomfort:轻量级的,只包含头文件的库,将C++ 11的一些新特性移植到C++03中。 Dlib:使用契约式编程和现代C++科技设计的通用的跨平台的C++库。 EASTL :EA-STL公共部分。 ffead-cpp :企业应用程序开发框架。 Folly:由Facebook开发和使用的开源C++库。 JUCE :包罗万象的C++类库,用于开发跨平台软件。 libPhenom:用于构建高性能和高度可扩展性系统的事件框架。 LibSourcey :用于实时的视频流和高性能网络应用程序的C++11 evented IO。 LibU : C语言写的多平台工具库。 Loki :C++库的设计,包括常见的设计模式和习语的实现。 MiLi :只含头文件的小型C++库。 openFrameworks :开发C++工具包,用于创意性编码。 Qt :跨平台的应用程序和用户界面框架。 Reason :跨平台的框架,使开发者能够更容易地使用Java,.Net和Python,同时也满足了他们对C++性能和优势的需求。 ROOT :具备所有功能的一系列面向对象的框架,能够非常高效地处理和分析大量的数据,为欧洲原子能研究机构所用。 STLport:是STL具有代表性的版本。 STXXL:用于额外的大型数据集的标准模板库。 Ultimate++ :C++跨平台快速应用程序开发框架。 Windows Template Library:用于开发Windows应用程序和UI组件的C++库。 Yomm11 :C++11的开放multi-methods。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值