本篇博客主要对实现服务端的主要功能,如:对主机配对请求的响应、对文件列表请求的响应、对文件下载请求的响应。
服务器示例
首先,我们先来搭建一个很简单的服务器,打印一下hello, world!来了解一下httplib库的简单使用。
代码如下:
#include "httplib.h"
using namespace httplib;
// 回调函数,对"/"请求进行响应
void HelloWorld(const Request& req, Response& res){
// 响应状态码
res.status = 200;
// 正文
res.body = "hello, world!";
}
int main(){
// 实例化服务器对象
Server _ser;
// 对"/"进行响应
_ser.Get("/", HelloWorld);
// 监听
_ser.listen("0.0.0.0", 9999);
return 0;
}
编译运行该程序:
打开浏览器,输入IP地址端口号以及URI:
从上述结果可以看出,服务器搭建成功。下面,我们将代码修改一下,让内容以html的形式显示。
#include "httplib.h"
using namespace httplib;
// 回调函数,对"/"请求进行响应
void HelloWorld(const Request& req, Response& res){
// 响应状态码
res.status = 200;
// 设置数据流类型
res.set_header("Content-Type", "text/html");
// 正文
res.body = "<h1>hello, world!</h1>";
}
int main(){
// 实例化服务器对象
Server _ser;
// 对"/"进行响应
_ser.Get("/", HelloWorld);
// 监听
_ser.listen("0.0.0.0", 9999);
return 0;
}
编译运行,然后打开浏览器查看:
服务端功能实现
我们的服务端一共需要实现三个功能:
- 对主机配对请求的响应;
- 对文件列表请求的响应;
- 对文件下载请求的响应;
首先,我们先来写一下整体的代码框架。
#include "httplib.h"
using namespace httplib;
// LocalFileShared服务器
class LFServer{
public:
// 服务器启动
void Start(uint16_t port){
// 主机配对请求响应
_ser.Get("/hostmatching", hostMatching);
// 文件列表获取响应
_ser.Get("/filelist", fileList);
// 文件下载响应
// (.*)正则表达式,匹配任意字符任意次
_ser.Get("/filelist/(.*)", fileDownload);
// 监听
_ser.listen("0.0.0.0", port);
}
private:
// 响应附近主机配对请求
static void hostMatching(const Request& req, Response& res);
// 响应附近主机获取文件列表请求
static void fileList(const Request& req, Response& res);
// 响应附近主机文件下载请求
static void fileDownload(const Request& req, Response& res);
private:
// 服务器对象
Server _ser;
};
然后,我们再来写一个makefile:
主机配对
主机配对请求的响应只需要给一个200状态码即可。
// 响应附近主机配对请求
static void hostMatching(const Request& req, Response& res){
// 配对成功
res.status = 200;
}
我们来测试一下,编译运行程序:
打开浏览器,输入IP地址端口号以及URI:
没有报错,匹配成功。
文件列表
首先,我们需要新建一个共享文件夹,我们就在工程目录下新建。
我们在Shared_Dir文件夹内创建几个文件,写入一些内容。
上面只是示例,剩下的自行创建。
然后,我们在代码中定义一个宏,表示该文件夹的路径。然后,我们需要将文件夹下的所有文件名写入响应的正文中,所以,我们来实现一下对一个目录文件的遍历。
为了可移植性,这里我们使用boost库中的接口对目录进行遍历。
Linux下安装boost库:
[sss@aliyun project]$ sudo yum install boost
[sss@aliyun project]$ sudo yum install boost-devel
查看是否安装成功:
使用boost库遍历目录下文件:
#include <iostream>
#include <string>
#include <boost/filesystem.hpp>
// 取别名
namespace bf = boost::filesystem;
int main(){
// 要遍历的路径
std::string targetPath = "./Shared_Dir";
// 路径
bf::path myPath(targetPath);
// 结束位置
bf::directory_iterator end;
for(bf::directory_iterator it(myPath); it != end; ++it){
// 不是目录文件,打印
if(!bf::is_directory(*it)){
std::cout << it->path().string() << std::endl;
}
}
return 0;
}
注意,编译时不要忘记链接boost_system、boost_filesystem库,不要忘了-std=c++11。
将遍历目录的结果放入响应报文的正文部分。
// 响应附近主机获取文件列表请求
static void fileList(const Request& req, Response& res){
// 获取成功
res.status = 200;
// 指定共享目录
bf::path share_path(SHARED_DIR);
// 结束位置
bf::directory_iterator end;
// 迭代器开始位置
bf::directory_iterator dit(share_path);
while(dit != end){
if(!bf::is_directory(*dit)){
// 将遍历到的非目录文件放入正文
res.body += dit->path().string() + "\n";
}
++dit;
}
}
由于我们使用了boost库,我们修改一下makefile。
编译运行程序:
打开浏览器,输入IP端口号以及URI:
从浏览器上打印的信息,我们可以看出,文件列表获取成功,但是我们可以看到文件所在路径,这对我们来说是不安全的,而且打印的字体有点丑,我们来优化一下,代码修改如下:
// 响应附近主机获取文件列表请求
static void fileList(const Request& req, Response& res){
// 获取成功
res.status = 200;
// 指定共享目录
bf::path share_path(SHARED_DIR);
// 结束位置
bf::directory_iterator end;
// 迭代器开始位置
bf::directory_iterator dit(share_path);
// 设置数据流类型
res.set_header("Content-Type", "text/html");
// 无序列表
res.body += "<ul>";
while(dit != end){
if(!bf::is_directory(*dit)){
// 将遍历到的非目录文件放入正文
std::string temp = dit->path().string();
// 找到最后一个/所在下标
size_t pos = temp.find_last_of('/');
// 截取出文件名
std::string result = temp.substr(pos + 1, std::string::npos);
// 无序列表中的每一项
res.body += "<li>";
// 放入正文
res.body += result + "\n";
res.body += "</li>";
}
++dit;
}
res.body += "</ul>";
}
编译运行,打开浏览器查看如下:
文件下载
最后,我们来实现文件的下载。首先,我们先来写一个简单的文件下载的测试程序。
#include "httplib.h"
#include <fstream>
#include <sstream>
using namespace httplib;
// 文件下载
void Download(const Request& req, Response& res){
// 成功
res.status = 200;
// 拿到请求路径
std::string temp = req.path;
// 切割出文件名
size_t pos = (temp).find_last_of('/');
// 组合出一个完成路径
std::string path = "./Shared_Dir/";
path += temp.substr(pos + 1, std::string::npos);
// 打开文件
std::ifstream ifs(path.c_str(), std::ios::binary);
// 缓冲区
std::ostringstream buf;
// 将文件内容放入缓冲区
buf << ifs.rdbuf();
// 文件内容放入响应正文
res.body = buf.str();
}
int main(){
// 服务器对象
Server _ser;
// 响应文件下载
_ser.Get("/filelist/(.*)", Download);
// 监听
_ser.listen("0.0.0.0", 9999);
return 0;
}
编译运行程序:
打开浏览器,输入IP端口号、URI查看:
这并不是我们想要的结果,我们需要的是将文件下载下来,所以我们需要将其下载下来。代码修改如下:
#include "httplib.h"
#include <fstream>
#include <sstream>
#include <boost/filesystem.hpp>
#include <iostream>
using namespace httplib;
// 取别名
namespace bf = boost::filesystem;
// 文件下载
void Download(const Request& req, Response& res){
// 成功
res.status = 200;
// 二进制流,将文件下载下来
res.set_header("Content-Type", "application/octet-stream");
// 拿到请求路径
std::string temp = req.path;
// 切割出文件名
size_t pos = temp.find_last_of('/');
// 组合出一个完成路径
std::string path = "./Shared_Dir/";
path += temp.substr(pos + 1, std::string::npos);
// 获取文件大小
size_t fsize = bf::file_size(path);
// 打开文件
std::fstream fs(path.c_str(), std::ios::binary | std::ios::in);
// 文件未打开
if(!fs.is_open()){
return;
}
// 设置body的空间大小
res.body.resize(fsize);
// 将文件内容读取到响应正文
fs.read(&res.body[0], fsize);
// 关闭文件
fs.close();
}
int main(){
// 服务器对象
Server _ser;
// 响应文件下载
_ser.Get("/filelist/(.*)", Download);
// 监听
_ser.listen("0.0.0.0", 9999);
return 0;
}
编译运行如下:
浏览器查看,输入IP端口号、URI后效果如下:
可以看到文件下载成功,但是是不是同一个文件呢,首先我们来看一下文件内容:
只是文件内容相同,还不能说明问题,我们再使用md5编码来对比一下,文件下载是否成功:
这里是将传到windows下的文件通过rz命令传到Linux下进行比对,发现是完全一样的,说明文件下载成功。
下面,我们在整体框架中加入文件下载模块:
// 响应附近主机文件下载请求
static void fileDownload(const Request& req, Response& res){
// 成功
res.status = 200;
// 二进制流,将文件下载下来
res.set_header("Content-Type", "application/octet-stream");
// 拿到请求路径
std::string temp = req.path;
// 切割出文件名
size_t pos = (temp).find_last_of('/');
// 组合出完整路径
std::string path = SHARED_DIR;
path += '/' + temp.substr(pos + 1, std::string::npos);
// 打开文件
std::ifstream ifs(path.c_str(), std::ios::binary);
// 缓冲区
std::ostringstream buf;
// 将文件内容放入缓冲区
buf << ifs.rdbuf();
// 文件内容放入响应正文
res.body = buf.str();
}
编译运行程序:
打开浏览器,输入IP端口号、URI查看:
最后,服务端所有代码如下:
#include "httplib.h"
#include <boost/filesystem.hpp>
using namespace httplib;
// 取别名
namespace bf = boost::filesystem;
// 共享目录
#define SHARED_DIR "./Shared_Dir"
// LocalFileShared服务器
class LFServer{
public:
// 服务器启动
void Start(uint16_t port){
// 主机配对请求响应
_ser.Get("/hostmatching", hostMatching);
// 文件列表获取响应
_ser.Get("/filelist", fileList);
// 文件下载响应
// (.*)正则表达式,匹配任意字符任意次
_ser.Get("/filelist/(.*)", fileDownload);
// 监听
_ser.listen("0.0.0.0", port);
}
private:
// 响应附近主机配对请求
static void hostMatching(const Request& req, Response& res){
// 配对成功
res.status = 200;
}
// 响应附近主机获取文件列表请求
static void fileList(const Request& req, Response& res){
// 获取成功
res.status = 200;
// 指定共享目录
bf::path share_path(SHARED_DIR);
// 结束位置
bf::directory_iterator end;
// 迭代器开始位置
bf::directory_iterator dit(share_path);
// 设置数据流类型
res.set_header("Content-Type", "text/html");
// 无序列表
res.body += "<ul>";
while(dit != end){
if(!bf::is_directory(*dit)){
// 将遍历到的非目录文件放入正文
std::string temp = dit->path().string();
// 找到最后一个/所在下标
size_t pos = temp.find_last_of('/');
// 截取出文件名
std::string result = temp.substr(pos + 1, std::string::npos);
// 无序列表中的每一项
res.body += "<li>";
// 放入正文
res.body += result + "\n";
res.body += "</li>";
}
++dit;
}
res.body += "</ul>";
}
// 响应附近主机文件下载请求
static void fileDownload(const Request& req, Response& res){
// 成功
res.status = 200;
// 二进制流,将文件下载下来
res.set_header("Content-Type", "application/octet-stream");
// 拿到请求路径
std::string temp = req.path;
// 切割出文件名
size_t pos = temp.find_last_of('/');
// 组合出完整路径
std::string path = SHARED_DIR;
path += '/' + temp.substr(pos + 1, std::string::npos);
// 获取文件大小
size_t fsize = bf::file_size(path);
// 打开文件
std::fstream fs(path.c_str(), std::ios::binary | std::ios::in);
// 文件未打开
if(!fs.is_open()){
return;
}
// 设置body的空间大小
res.body.resize(fsize);
// 将文件内容读取到响应正文
fs.read(&res.body[0], fsize);
// 关闭文件
fs.close();
}
private:
// 服务器对象
Server _ser;
};
int main(){
// LFS服务器对象
LFServer _lfs;
// 启动服务器
_lfs.Start(9999);
return 0;
}
makefile文件如下:
server:server.cc
g++ $^ -std=c++0x -lpthread -o $@ -lboost_system -lboost_filesystem
.PHONY:clean
clean:
rm server