⚡通信管理模块⚡
Posted 努力学习的少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了⚡通信管理模块⚡相关的知识,希望对你有一定的参考价值。
目录
🌳.通信管理模块的介绍
通信管理模块管理着服务器与浏览器之间的通信:
- 当服务器收到浏览器的 get /listshow/ http/1.1请求时,服务器就会給浏览器响应一个文件展示页面,用户可以再这个页面上传文件或下载文件。
- 当服务器收到浏览器的 个 get /download/文件名 http1.1请求时,服务器就会到backdir目录下或
- packdir目录下查找该文件,如果找到了,就将该文件发送給浏览器。
- 当服务器收到浏览器的 个post /upload/ http1.1请求时,则浏览器将文件上传到服务器上,文件名和文件数据存储在http响应中,服务器则将该文件放入backdir目录下。
🌳.通信模块的设计
将通信模块设计为单例模式,保证系统中只有一个通信模块对象.
服务器在运行的时候,先注册各个请求与对应函数的关心
class Service
private:
std::string ip;
int port;
httplib::Server server;
static std::mutex lock;
static Service* instant;
private:
Service()
ip=sjp::Config::GetInstant()->GetIp();
port=sjp::Config::GetInstant()->GetServerPort();
Service(Service&)=delete;
static std::string mtimeToString(time_t mtime)
std::string res= ctime(&mtime);
return res;
public:
void Run()
server.Post("/upload",UpLoad);
server.Get("/download/(.*)",DownLoad);
server.Get("/listshow",ListShow);
server.Get("/",ListShow);
server.listen("0.0.0.0",port);//启动服务器
static Service* GetInstant()
if(instant==nullptr)
lock.lock();
if(instant==nullptr)
instant=new Service();
lock.unlock();
return instant;
;
std::mutex Service::lock;
Service* Service::instant=nullptr;
🌳.ListShow函数
listshow页面的前端源代码如下:
- 我们需要使用stringstream对象将前端页面的源代码组织起来,然后将在将stringstream组织在http响应中,httplib服务器在底层会将http响应发送給浏览器。
- 为了保证浏览器能够将前端代码翻译成一个页面,需要在httplib响应中的Content-type字段设置为text/html.
static void ListShow(const httplib::Request& req,httplib::Response& rep)
cout<<"listshow"<<endl;
std::vector<sjp::FileInfo> arry;
sjp::FileInfoManger::GetInstant()->GetAllInfo(arry);
std::stringstream ss;
//
ss<<"<!DOCTYPE html>"<<"<html><meta charset=\\"UTF-8\\"><head><title>Download</title></head><body><h1>Download</h1><table>";
ss<<"<tr><td><h3>文件名</h3></a></td>";
ss<<"<td align=\\"left\\">"<<"<h3>最近修改时间</h3>"<<"</td>";
ss<<"<td align=\\"right\\">"<<"<h3>文件大小</h3>"<<"</td></tr>";
W> for(int i=0;i<arry.size();i++)
//>
/*<td><a href="/download/test.txt">test.txt</a></td>
<td align="right">2021-12-29 10:10:10</td>
<td align="right">28k</td>
* */
std::string path=arry[i].back_path;
sjp::FileUtil fu(path);
std::string atimestr=mtimeToString(arry[i].modify_time);
ss<<"<tr><td><a href=\\"/download/"<<fu.GetFilename()<<"\\">"<<fu.GetFilename()<<"</a></td>";
ss<<"<td align=\\"right\\">"<<atimestr<<" </td>";
ss<<"<td align=\\"right\\">"<<arry[i].file_size/1024<<"k"<<"</td></tr>";
ss<<"</table></body></html>";
ss<<"<form action=\\"http://119.23.41.13:8081/upload\\" method=\\"post\\" enctype=\\"multipart/form-data\\"><div><input type=\\"file\\" name=\\"file\\"></div><div><input type=\\"submit\\" va lue=\\"上传\\"></div></form>";
rep.body=ss.str();
rep.status=200;
rep.set_header("Content-Type","text/html;");
🌳.UpLoad函数
post /upload/ http1.1请求如下
上传文件的请求中请求正文包含文件的字段名和文件名,文件正文(文件包含的数据)。
实现:
- 服务器收到上传请求后,需要先判断是否有文件字段名,如果有则提取该文件的信息。
- 然后使用FileUtil文件工具打开一个文件存放在backdir下,并将文件正文写入到该文件中,并新插入一个文件信息到文件管理系统中_table表中,则完成服务器上传文件的请求。
- 重新返回一个文件页面。
static void UpLoad(const httplib::Request& req,httplib::Response& rep)
/* 1.判断是否有文件上传
* 2.如果有,则获取该文件的名称和内容
* 3.在backdir中创建一个文件
* 4.将文件信息存储到FileInfoManger中
* */
//判断是否有文件上传
std::cout<<"upload begin"<<endl;
auto res=req.has_file("file");
if(res==false)
rep.status=400;
return;
const auto& file=req.get_file_value("file");
std::string filepath=sjp::Config::GetInstant()->GetBackDir()+file.filename;
std::string body=file.content;
sjp::FileUtil fu(filepath);
fu.SetContent(body);
sjp::FileInfo fileinfo(filepath);
sjp::FileInfoManger::GetInstant()->Insert(fileinfo);
sjp::FileInfoManger::GetInstant()->Storage();
ListShow(req,rep);
🌳.DownLoad函数
- 当服务器收到一个下载请求时,服务器需要执行的步骤:
- 文件信息管理模块通过请求url获取该文件信息,如果没有该文件信息,则说明该文件不存在,返回404.
- 如果存在该文件,则通过文件信息中的packsign判断是否被压缩
- 如果packsign=false,说明该文件没有没有被压缩,则服务器直接从backdir目录下将获取文件内容发送給浏览器。
- 如果packsign=true,说明该文件已经被压缩,则服务器从packdir目录下将该文件进行解压缩到backdir,并删除packdir目录下的压缩包,将文件信息中的packsign修改为false,最后将文件内容发送給浏览器。
- 如果浏览器想要以下载的形式接受文件,则服务器需要将http响应中的Content-Type字段设置为application/octet-stream。
下载的断点续传
用户在下载文件的过程中,可能因为网络或者其它原因导致服务器下载失败了,如果没有断点续传,则用户重新下载文件则需要从文件的最开始的位置下载,如果实现了断点续传,则用户重新下载 文件则会从上次失败的位置开始下载。
http下载的断点续传的思想
当浏览器第一次下载的时候,服务器給浏览器发送的http响应添加Accept-Ranges和Etag这两个字段:
- Accept-Ranges一般被设置为bytes ,表示这个字段表示服务器支持断点续传,以字节为单位传输数据.
- ETag表示的是服务器上某一版本资源的唯一标识,如果资源被改动过,则ETag会改变,客户端收到后则会保证这个信息。
如果下载中断了,浏览器重新下载文件,则第二次的http请求中需要包含If-Range字段和Range字段.
- If-Range字段:"保存服务端的响应的ETag字段的信息",用于判断与服务端中与上一次请求资源是否一致,如果一致则断点续传,如果不一致,则重新开始下载。
- Range字段:bytes start-end,表示的是请求服务器资源从start个字节开始到end个字节结束的数据。
则服务端发送http响应給客户端需要包含Content-Range字段和ETag字段:
- Content-Range:start-end/文件大小,表示http响应包含文件数据从start开始到end结束的文件数据,文件大小表示文件总大小。
- ETag:表示的是服务器上资源的唯一标识。
ETag字段的设定
ETag某一版本资源的唯一标识,如果资源被改动过,则ETag则会改变,所以我们可以通过"文件名-文件大小-文件修改时间"来设定ETag(当然这不是唯一的方式)。
UpDown函数的实现
如果浏览器发生断点续传请求,则在httplib内部实现了对Range字段的解析,开发人员只需要读取文件中的内容,设定好字段,底层的httplib库则会自动定位好具体的数据范围,发送給浏览器。
//文件名-文件大小-最后一次修改时间
static std::string GetEtag(std::string filename)
sjp::FileUtil fu(filename);
std::string etag=fu.GetFilename();
etag+="-";
etag+=std::to_string(fu.GetFileSize());
etag+="-";
etag+=std::to_string(fu.GetFileModfityTime());
return etag;
static void DownLoad(const httplib::Request& req,httplib::Response& rep)
cout<<"download begin"<<endl;
std::string url=req.path;
sjp::FileInfo fileinfo;
sjp::FileInfoManger::GetInstant()->GetoneByURL(url,fileinfo);
if(!sjp::FileInfoManger::GetInstant()->Exist(fileinfo.back_path))
/* sjp::FileUtil fu(back_dir);
std::vector<std::string> arry;
fu.GetPathInDir(arry);
*/
rep.status=404;
return ;
if(fileinfo.pack_sign==true)
//解压缩
sjp::FileUtil fu(fileinfo.pack_path);
fu.UnpackFile(fileinfo.back_path);
fileinfo.pack_sign=false;
sjp::FileInfoManger::GetInstant()->Insert(fileinfo);
fu.Remove();
bool flag=false;
//判断是否为断点续传
if(req.has_header("If-Range"))
//断点续传
std::string oldetag=req.get_header_value("If-Range");
if(oldetag==GetEtag(fileinfo.back_path))
//需要断点续传
cout<<"oldetag: "<<oldetag<<endl;
cout<<"etag: "<<GetEtag(fileinfo.back_path)<<endl;
flag=true;
sjp::FileUtil fu(fileinfo.back_path);
//获取文件内容
std::string body;
fu.GetContent(body);
rep.body=body;
rep.content_length_=body.size();
if(flag==false)
//不需要断点续传
rep.status=200;
rep.set_header("Accept-Ranges","bytes");
rep.set_header("Content-Type","application/octet-stream");
rep.set_header("Etag",GetEtag(fileinfo.back_path));
else
//断点续传
rep.status=206;
rep.set_header("Accept-Ranges","bytes");
rep.set_header("Content-Type","application/octet-stream");
rep.set_header("ETag",GetEtag(fileinfo.back_path));
return ;
以上是关于⚡通信管理模块⚡的主要内容,如果未能解决你的问题,请参考以下文章