本篇博客主要对实现服务端的主要功能,如:对主机配对请求的响应、对文件列表请求的响应、对文件下载请求的响应。

服务器示例

首先,我们先来搭建一个很简单的服务器,打印一下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;
}

编译运行该程序

java实现windows共享盘的文件写入_#include


打开浏览器,输入IP地址端口号以及URI

java实现windows共享盘的文件写入_文件下载_02


上述结果可以看出,服务器搭建成功。下面,我们将代码修改一下,让内容以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;
}

编译运行,然后打开浏览器查看

java实现windows共享盘的文件写入_#include_03


java实现windows共享盘的文件写入_文件下载_04

服务端功能实现

我们的服务端一共需要实现三个功能

  1. 对主机配对请求的响应
  2. 对文件列表请求的响应
  3. 对文件下载请求的响应

首先,我们先来写一下整体的代码框架

#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

java实现windows共享盘的文件写入_名字待定_05

主机配对

主机配对请求的响应只需要给一个200状态码即可

// 响应附近主机配对请求
static void hostMatching(const Request& req, Response& res){
	// 配对成功
	res.status = 200;
}

我们来测试一下,编译运行程序:

java实现windows共享盘的文件写入_#include_06


打开浏览器,输入IP地址端口号以及URI

java实现windows共享盘的文件写入_文件下载_07


没有报错,匹配成功

文件列表

首先,我们需要新建一个共享文件夹,我们就在工程目录下新建。

java实现windows共享盘的文件写入_文件下载_08


我们在Shared_Dir文件夹内创建几个文件,写入一些内容

java实现windows共享盘的文件写入_#include_09


上面只是示例,剩下的自行创建


然后,我们在代码中定义一个宏,表示该文件夹的路径。然后,我们需要将文件夹下的所有文件名写入响应的正文中,所以,我们来实现一下对一个目录文件的遍历
为了可移植性,这里我们使用boost库中的接口对目录进行遍历
Linux下安装boost库

[sss@aliyun project]$ sudo yum install boost
[sss@aliyun project]$ sudo yum install boost-devel

查看是否安装成功

java实现windows共享盘的文件写入_名字待定_10


使用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

java实现windows共享盘的文件写入_文件下载_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

java实现windows共享盘的文件写入_名字待定_12


编译运行程序

java实现windows共享盘的文件写入_文件下载_13


打开浏览器,输入IP端口号以及URI

java实现windows共享盘的文件写入_#include_14


从浏览器上打印的信息,我们可以看出,文件列表获取成功,但是我们可以看到文件所在路径,这对我们来说是不安全的,而且打印的字体有点丑,我们来优化一下代码修改如下

// 响应附近主机获取文件列表请求
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>";
}

编译运行,打开浏览器查看如下

java实现windows共享盘的文件写入_服务器_15


java实现windows共享盘的文件写入_名字待定_16

文件下载

最后,我们来实现文件的下载。首先,我们先来写一个简单的文件下载的测试程序

#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;
}

编译运行程序

java实现windows共享盘的文件写入_文件下载_17


打开浏览器,输入IP端口号、URI查看

java实现windows共享盘的文件写入_服务器_18


这并不是我们想要的结果,我们需要的是将文件下载下来,所以我们需要将其下载下来。代码修改如下:

#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;
}

编译运行如下

java实现windows共享盘的文件写入_服务器_19


浏览器查看,输入IP端口号、URI后效果如下

java实现windows共享盘的文件写入_#include_20


可以看到文件下载成功,但是是不是同一个文件呢,首先我们来看一下文件内容

java实现windows共享盘的文件写入_文件下载_21


只是文件内容相同,还不能说明问题,我们再使用md5编码来对比一下,文件下载是否成功

java实现windows共享盘的文件写入_服务器_22


这里是将传到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();
}

编译运行程序

java实现windows共享盘的文件写入_文件下载_23


打开浏览器,输入IP端口号、URI查看

java实现windows共享盘的文件写入_文件下载_24


最后,服务端所有代码如下

#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