『小项目』局域网文件共享(二)

本篇博客主要实现客户端主要功能,如:获取局域网在线主机列表、获取指定主机的共享文件列表、下载指定主机的指定文件。

客户端请求示例

我们首先来实现一个简单的客户端请求,首先我们来测试一下对服务器发送一个主机配对请求

#include "httplib.h"
#include <iostream>

using namespace httplib;

int main(){
	// 实例化一个客户端对象
	Client _cli("172.17.59.128", 9999);

	// 获取服务端响应
	auto res = _cli.Head("/hostmatching");

	// 判断响应状态码,如果为200,表示主机配对成功
	if(res->status == 200){
		std::cout << "host matching successful!" << std::endl;
	}
	else{
		std::cout << "host matching failed!" << std::endl;
	}

	return 0;
}

首先,我们先启动服务器
在这里插入图片描述
然后,编译运行该客户端请求
在这里插入图片描述
可以看到,主机配对成功

客户端功能实现

我们的客户端一共有以下三个功能

  1. 发现局域网主机,进行主机配对
  2. 获取指定主机的共享文件列表
  3. 下载指定主机的指定文件

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

#include "httplib.h"
#include <ifaddrs.h>
#include <iostream>
#include <boost/algorithm/string.hpp>

using namespace httplib;

// LocalFileShared客户端
class LFSClient{
	public:
		// 客户端启动
		void Start(){
			while(1){
				// 主界面
				mainInterface();

				int choose = 0;
				std::cout << "请选择您要进行的操作: ";
				std::cin >> choose;

				switch(choose){
					case 1:
						getOnlineHost(); break;
					case 2:
						getFileList(); break;
					case 3:
						downloadFile(); break;
					default :
						return;
				}
			}
		}

	private:
		void mainInterface(){
			std::cout << "===============\n";
			std::cout << "1. 获取在线主机" << std::endl;
			std::cout << "2. 获取文件列表" << std::endl;
			std::cout << "3. 下载指定文件" << std::endl;
			std::cout << "others. 退出" << std::endl;
			std::cout << "===============\n";
		}

		// 获取局域网所有主机
		bool getLocalHost();

		// 获取在线主机列表
		bool getOnlineHost();

		// 获取指定主机的共享文件列表
		bool getFileList();

		// 下载指定主机的指定文件
		bool downloadFile();

	private:
		// 主机在主机列表中的下标
		int _host_idx;

		// 保存获取到的主机列表
		std::vector<std::string> _host_list;

		// 保存在线主机列表
		std::vector<std::string> _online_list;

		// 保存获取到的文件列表
		std::vector<std::string> _file_list;
};

int main(){
	// LFS客户端对象
	LFSClient _lfs;

	// 启动客户端
	_lfs.Start();

	return 0;
}

在线主机列表

在线主机列表的获取比较简单,我们只需要算出局域网内所有的主机IP地址
首先我们来获取局域网中所有的主机IP,因为是要获取局域网内的所有主机IP地址,所以我们只需要获取本机的IP地址和子网掩码,通过主机IP地址和子网掩码按位与就可以得到网络号,然后对子网掩码取反就可以得到该局域网内主机数量
首先,我们先来介绍一下我们使用的接口

// 获取本机网卡信息
int getifaddrs(struct ifaddrs **ifap);

// 释放网卡信息存储资源
void freeifaddrs(struct ifaddrs *ifa);

// ifaddrs结构体
struct ifaddrs {
	struct ifaddrs  *ifa_next;    /* Next item in list */
	char            *ifa_name;    /* Name of interface */
	unsigned int     ifa_flags;   /* Flags from SIOCGIFFLAGS */
	struct sockaddr *ifa_addr;    /* Address of interface */
	struct sockaddr *ifa_netmask; /* Netmask of interface */
	union {
		/* Broadcast address of interface */
		struct sockaddr *ifu_broadaddr;
		/* Point-to-point destination address */
		struct sockaddr *ifu_dstaddr;
	} ifa_ifu;
#define              ifa_broadaddr ifa_ifu.ifu_broadaddr
#define              ifa_dstaddr   ifa_ifu.ifu_dstaddr
	void            *ifa_data;    /* Address-specific data */
};

获取本机IP地址和子网掩码

#include <ifaddrs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <iostream>

int main(){
	struct ifaddrs* addrs = nullptr;

	// 本机IP
	std::string ip;
	ip.resize(INET_ADDRSTRLEN);
	
	// 子网掩码
	std::string mask;
	mask.resize(INET_ADDRSTRLEN);

	// 获取本机网卡信息
	getifaddrs(&addrs);

	while(addrs != nullptr){
		if(addrs->ifa_addr->sa_family == AF_INET){
			// 获取IP地址
			// 32位二进制转换为点分十进制数
			inet_ntop(
				AF_INET, &((struct sockaddr_in*)(addrs->ifa_addr))->sin_addr,
				&ip[0], INET_ADDRSTRLEN
			);

			// 跳过回环网卡
			if(ip == "127.0.0.1"){
				addrs = addrs->ifa_next;
				continue;
			}

			// 获取子网掩码
			// 32位二进制转换为点分十进制数
			inet_ntop(
				AF_INET, &((struct sockaddr_in*)(addrs->ifa_netmask))->sin_addr, 
				&mask[0], INET_ADDRSTRLEN
			);
		}

		addrs = addrs->ifa_next;
	}

	// 释放存储网卡信息的资源
	freeifaddrs(addrs);

	// 打印本机IP和子网掩码
	std::cout << "ip: " << ip << std::endl;
	std::cout << "mask: " << mask << std::endl;

	return 0;
}

编译运行,结果如下
在这里插入图片描述


获取到本机的IP地址和子网掩码后,我们可以根据计算得到的网络号和主机数量,来遍历局域网内的所有主机,下面我们来计算一下本机所在局域网内所有主机的IP地址,代码如下:

#include <ifaddrs.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <iostream>
#include <vector>

int main(){
	struct ifaddrs* addrs = nullptr;

	// 本机IP
	std::string ip;
	// 子网掩码
	std::string mask;

	// 获取本机网卡信息
	getifaddrs(&addrs);

	while(addrs != nullptr){
		if(addrs->ifa_addr->sa_family == AF_INET){
			// 保存转换出的点分十进制
			ip.resize(INET_ADDRSTRLEN);
			mask.resize(INET_ADDRSTRLEN);

			// 获取IP地址
			// 32位二进制转换为点分十进制数
			inet_ntop(
				AF_INET, &((struct sockaddr_in*)(addrs->ifa_addr))->sin_addr,
				&ip[0], INET_ADDRSTRLEN
			);

			// 跳过回环网卡
			if(ip == "127.0.0.1"){
				addrs = addrs->ifa_next;
				continue;
			}

			// 获取子网掩码
			// 32位二进制转换为点分十进制数
			inet_ntop(
				AF_INET, &((struct sockaddr_in*)(addrs->ifa_netmask))->sin_addr, 
				&mask[0], INET_ADDRSTRLEN
			);
		}

		addrs = addrs->ifa_next;
	}

	// 释放存储网卡信息的资源
	freeifaddrs(addrs);

	// 打印本机IP和子网掩码
	std::cout << "ip: " << ip << std::endl;
	std::cout << "mask: " << mask << std::endl;

	// 子网掩码
	uint32_t mask_n;
	inet_pton(AF_INET, &mask[0], (void*)&mask_n);
	uint32_t mask_h = ntohl(mask_n);

	// 主机数量
	uint32_t hostNum = ~mask_h;

	// IP地址
	uint32_t ip_n;
	inet_pton(AF_INET, &ip[0], (void*)&ip_n);
	uint32_t ip_h = ntohl(ip_n);

	// 网络号
	uint32_t networkNum_h = ip_h & mask_h;

	// 创建主机列表,保存局域网内所有主机
	std::vector<std::string> _host_list;


	// 保存当前遍历到的主机
	std::string hosti;
	hosti.resize(INET_ADDRSTRLEN);

	// 遍历所有主机,将其保存到主机列表中
	// 这里偷个懒,只遍历172.17.59.0 ~ 172.17.59.255
	for(uint32_t i = 2816; i <= 3071; ++i){
		// 计算局域网内主机
		uint32_t hosti_h = networkNum_h + i;
		// 转为网络字节序
		uint32_t hosti_n = htonl(hosti_h);

		// IP地址二进制形式转换为点分十进制
		inet_ntop(AF_INET, &hosti_n, &hosti[0], INET_ADDRSTRLEN);

		// 放入主机列表
		_host_list.push_back(hosti);
	}

	// 打印主机列表中的所有IP地址
	int format = 0;
	for(size_t i = 0; i < _host_list.size(); ++i){
		std::cout << _host_list[i] << "\t";

		// 控制一行打印9个IP地址
		++format;
		if(format == 9){
			format = 0;
			std::cout << std::endl;
		}
	}
	std::cout << std::endl;

	return 0;
}

在这里插入图片描述
这里偷个懒,只遍历172.17.59.0~172.17.59.255,因为这里碰到了一个问题,如果遍历所有的主机IP,会出问题,问题如下
在这里插入图片描述
最后一个字节的8位发生进位时,会出问题;原因暂时不详,我们先跳过这个问题,先实现基本功能,后面再回来解决该问题


获取到局域网内所有主机IP地址后,我们来对局域网内所有的IP地址都发送一个主机配对请求,将配对成功的主机添加到在线主机列表

// 获取在线主机列表
bool getOnlineHost(){
	// 获取局域网内所有主机
	getLocalHost();

	for(size_t i = 0; i < _host_list.size(); ++i){
		// 实例化一个客户端,等待时间设置为1s
		Client _cli(_host_list[i].c_str(), 9999, 1);

		// 发送获取主机配对请求
		auto res = _cli.Head("/hostmatching");

		// 主机配对失败
		if(res == nullptr){
			std::cout << _host_list[i] << " matching failed!\n";
			continue;
		}

		// 响应成功,将其添加到在线主机列表
		if(res->status == 200){
			_online_list.push_back(_host_list[i]);
			std::cout << _host_list[i] << " matching successed!\n";
		}
		// 响应失败
		else{
			std::cout << _host_list[i] << " matching failed!\n";
		}
	}
	
	// 打印在线主机
  	std::cout << "The Online Host is Below: \n";
  	for(size_t i = 0; i < _online_list.size(); ++i){
  		std::cout << i << ". " << _online_list[i] << std::endl;
  	}

	return true;
}

首先,启动服务器
在这里插入图片描述
然后,我们编译运行客户端,为了演示效果,我们对172.17.59.120~172.17.59.130范围内的主机进行配对
在这里插入图片描述
可以看到,172.17.59.128主机配对成功

共享文件列表

首先,我们先来写一个简单的测试客户端,用来获取指定主机的共享文件列表

#include "httplib.h"
#include <iostream>

using namespace httplib;

int main(){
	// 实例化客户端
	Client _cli("172.17.59.128", 9999);

	// 向服务器发送请求并获取相应
	auto res = _cli.Get("/filelist");

	// 获取失败
	if(res == nullptr){
		std::cout << "Get FileList Failed!\n";

		return -1;
	}

	// 获取文件列表成功
	if(res->status == 200){
		std::cout << "The FileList is Below: \n";

		// 打印文件列表
		std::cout << res->body;
	}

	return 0;
}

首先,启动服务器
在这里插入图片描述
然后,编译运行程序
在这里插入图片描述
可以看到,获取文件列表成功


但是,我们需要将响应正文中以\n分隔的文件名放到一个文件列表中,所以需要对字符串进行切割,下面我们实现一个切分字符串的功能

#include "boost/algorithm/string.hpp"
#include <iostream>
#include <vector>

int main(){
	// 存放切分出来的字符串
	std::vector<std::string> result;

	// 待切分的字符串
	std::string before = "hello.txt\nhehe.cc";

	// 切分字符串,以'\n'为分割符
	boost::split(result, before, boost::is_any_of("\n"));

	// 结果打印
	for(size_t i = 0; i < result.size(); ++i){
		std::cout << i << ". " << result[i] << std::endl;
	}

	return 0;
}

编译运行,效果如下:
在这里插入图片描述


下面,我们将获取文件列表模块添加到整体框架中

// 获取指定主机的共享文件列表
bool getFileList(){
	// 选择指定主机
	int choose = 0;
	std::cout << "请选择主机号: ";
	std::cin >> choose;

	// 实例化客户端对象
	Client _cli(_online_list[choose].c_str(), 9999);

	// 向服务器发送获取文件列表请求并接收服务端返回的响应
	auto res = _cli.Get("/filelist");

	// 响应失败
	if(res == nullptr){
		return false;
	}

	// 将响应正文中的文件名切分出来,保存到文件列表中
	boost::split(_file_list, res->body, boost::is_any_of("\n"));

	// 文件列表打印
	std::cout << "The FileList is Below: \n";
	for(size_t i = 0; i < _file_list.size(); ++i){
		std::cout << i << ". " << _file_list[i] << std::endl;
	}

	return true;
}

首先,启动服务器
在这里插入图片描述
然后,编译运行客户端
在这里插入图片描述
可以看到,文件列表获取成功

文件下载

首先,我们先来写一个简单的文件下载响应的测试程序

#include "httplib.h"
#include <fstream>
#include <iostream>

using namespace httplib;

// 下载文件存放路径
#define DownLoad_Dir "./Download_Dir"

int main(){
	// 实例化客户端对象
	Client _cli("172.17.59.128", 9999);

	// 向服务端发送请求,并获取服务端响应
	auto res = _cli.Get("/filelist/hehe.cc");

	// 获取响应失败
	if(res == nullptr){
		return -1;
	}

	// 下载成功
	if(res->status == 200){
		// 打开文件
		std::fstream fs("./Download_Dir/hehe.cc", std::ios::out | std::ios::binary);

		// 将响应正文写入文件中
		fs.write(res->body.c_str(), 300);

		fs.close();
	}

	return 0;
}

编译运行程序,效果如下
在这里插入图片描述
我们可以看到,打开的文件中有一些乱码,这是因为,我们不知道文件的大小,所以在写入文件的时候,写了300个字节,但是响应的正文中没有这么多的数据,所以就会有一些乱码,我们使用get_header_value()接口获取一下正文的长度,然后按指定长度读取就不会有问题了

#include "httplib.h"
#include <fstream>
#include <iostream>

using namespace httplib;

// 下载文件存放路径
#define DownLoad_Dir "./Download_Dir"

int main(){
	// 实例化客户端对象
	Client _cli("172.17.59.128", 9999);

	// 向服务端发送请求,并获取服务端响应
	auto res = _cli.Get("/filelist/hehe.cc");

	// 获取正文长度
	std::string len = res->get_header_value("Content-Length");

	// 定义一个流,将字符串转为数字
	std::stringstream temp;
	temp << len;
	size_t fsize;
	temp >> fsize;

	// 获取响应失败
	if(res == nullptr){
		return -1;
	}

	// 下载成功
	if(res->status == 200){
		// 打开文件
		std::fstream fs("./Download_Dir/hehe.cc", std::ios::out | std::ios::binary);

		// 将响应正文写入文件中
		fs.write(res->body.c_str(), fsize);

		fs.close();
	}

	return 0;
}

编译运行,效果如下
在这里插入图片描述
可以看到,下载的文件和原文件的md5值是相同的,也就是说这两个文件是完全相同的,说明下载成功


最后,我们将文件下载模块加入到整体代码框架中

// 下载指定主机的指定文件
bool downloadFile(){
	// 选择要下载的文件
	int choose = 0;
	std::cout << "请输入要下载文件编号: ";
	std::cin >> choose;

	// 实例化客户端对象
	Client _cli(_online_list[_host_idx].c_str(), 9999);

	// 请求路径
	std::string path = "/filelist/";
	path += _file_list[choose];

	// 向服务端发送请求并且接收服务端的响应
	auto res = _cli.Get(path.c_str());
	if(res == nullptr){
		return false;
	}

	std::cout << "test!\n";

	// 下载成功
	if(res->status == 200){
		// 获取正文长度
		std::string len = res->get_header_value("Content-Length");

		// 定义一个流,将字符串转为数字
		std::stringstream temp;
		temp << len;
		size_t fsize;
		temp >> fsize;

		// 下载路径
		std::string path = "./Download_Dir/";
		path += _file_list[choose];

		// 打开文件
		std::fstream fs(path.c_str(), std::ios::out | std::ios::binary);

		// 将响应正文写入文件
		fs.write(res->body.c_str(), fsize);

		fs.close();

		// 下载成功
		std::cout << "File " << _file_list[choose] << " Download Success!\n";
	}

	return true;
}

首先,我们先来启动服务器
在这里插入图片描述
然后,编译运行程序,效果如下
在这里插入图片描述


整体的客户端代码如下

#include "httplib.h"
#include <ifaddrs.h>
#include <iostream>
#include <boost/algorithm/string.hpp>

using namespace httplib;

// LocalFileShared客户端
class LFSClient{
	public:
		// 客户端启动
		void Start(){
			while(1){
				// 主界面
				mainInterface();

				int choose = 0;
				std::cout << "请选择您要进行的操作: ";
				std::cin >> choose;

				switch(choose){
					case 1:
						getOnlineHost(); break;
					case 2:
						getFileList(); break;
					case 3:
						downloadFile(); break;
					default :
						return;
				}
			}
		}

	private:
		void mainInterface(){
			std::cout << "===============\n";
			std::cout << "1. 获取在线主机" << std::endl;
			std::cout << "2. 获取文件列表" << std::endl;
			std::cout << "3. 下载指定文件" << std::endl;
			std::cout << "others. 退出" << std::endl;
			std::cout << "===============\n";
		}

		// 获取局域网所有主机
		bool getLocalHost(){
			struct ifaddrs* addrs = nullptr;

			// 本机IP
			std::string ip;
			// 子网掩码
			std::string mask;

			// 获取本机网卡信息
			int ret = getifaddrs(&addrs);
			// 获取本机网卡信息失败
			if(ret < 0){
				return false;
			}

			while(addrs != nullptr){
				if(addrs->ifa_addr->sa_family == AF_INET){
					// 保存转换出的点分十进制
					ip.resize(INET_ADDRSTRLEN);
					mask.resize(INET_ADDRSTRLEN);

					// 获取IP地址
					// 32位二进制转换为点分十进制数
					inet_ntop(
						AF_INET, &((struct sockaddr_in*)(addrs->ifa_addr))->sin_addr,
						&ip[0], INET_ADDRSTRLEN
					);

					// 跳过回环网卡
					if(ip == "127.0.0.1"){
						addrs = addrs->ifa_next;
						continue;
					}

					// 获取子网掩码
					// 32为二进制转换为点分十进制数
					inet_ntop(
						AF_INET, &((struct sockaddr_in*)(addrs->ifa_netmask))->sin_addr,
						&mask[0], INET_ADDRSTRLEN
					);
				}

				addrs = addrs->ifa_next;
			}

			// 释放存储网卡信息的资源
			freeifaddrs(addrs);

			// 子网掩码
			uint32_t mask_n;
			inet_pton(AF_INET, &mask[0], &mask_n);
			uint32_t mask_h = ntohl(mask_n);

			// 主机数量
			uint32_t hostNum = ~mask_h;

			// IP地址
			uint32_t ip_n;
			inet_pton(AF_INET, &ip[0], &ip_n);
			uint32_t ip_h = ntohl(ip_n);

			// 网络号
			uint32_t networkNum_h = ip_h & mask_h;

			// 保存当前遍历到的主机
			std::string hosti;
			hosti.resize(INET_ADDRSTRLEN);

			// 遍历所有主机,将其保存到主机列表中
			for(uint32_t i = 2816; i <= 3071; ++i){
				// 计算局域网内主机
				uint32_t hosti_h = networkNum_h + i;
				// 转为网络字节序
				uint32_t hosti_n = htonl(hosti_h);

				// IP地址二进制形式转换为点分十进制
				inet_ntop(AF_INET, &hosti_n, &hosti[0], INET_ADDRSTRLEN);

				// 放入主机列表
				_host_list.push_back(hosti);
			}

			return true;
		}

		// 获取在线主机列表
		bool getOnlineHost(){
			// 获取局域网内所有主机
			getLocalHost();

			//for(size_t i = 0; i < _host_list.size(); ++i){
			for(size_t i = 128; i <= 128; ++i){
				// 实例化一个客户端
				Client _cli(_host_list[i].c_str(), 9999, 1);

				// 发送获取主机配对请求
				auto res = _cli.Head("/hostmatching");

				// 主机配对失败
				if(res == nullptr){
					continue;
				}

				// 响应成功,将其添加到在线主机列表
				if(res->status == 200){
					_online_list.push_back(_host_list[i]);
				}
			}

			// 打印在线主机
			std::cout << "The Online Host is Below: \n";
			for(size_t i = 0; i < _online_list.size(); ++i){
				std::cout << i << ". " << _online_list[i] << std::endl;
			}

			return true;
		}

		// 获取指定主机的共享文件列表
		bool getFileList(){
			// 选择指定主机
			std::cout << "请选择主机号: ";
			std::cin >> _host_idx;

			// 实例化客户端对象
			Client _cli(_online_list[_host_idx].c_str(), 9999);

			// 向服务器发送获取文件列表请求并接收服务端返回的响应
			auto res = _cli.Get("/filelist");

			// 响应失败
			if(res == nullptr){
				return false;
			}

			// 将响应正文中的文件名切分出来,保存到文件列表中
			boost::split(_file_list, res->body, boost::is_any_of("\n"));

			// 文件列表打印
			std::cout << "The FileList is Below: \n";
			for(size_t i = 0; i < _file_list.size(); ++i){
				std::cout << i << ". " << _file_list[i] << std::endl;
			}

			return true;
		}

		// 下载指定主机的指定文件
		bool downloadFile(){
			// 选择要下载的文件
			int choose = 0;
			std::cout << "请输入要下载文件编号: ";
			std::cin >> choose;

			// 实例化客户端对象
			Client _cli(_online_list[_host_idx].c_str(), 9999);

			// 请求路径
			std::string path = "/filelist/";
			path += _file_list[choose];

			// 向服务端发送请求并且接收服务端的响应
			auto res = _cli.Get(path.c_str());
			if(res == nullptr){
				return false;
			}

			std::cout << "test!\n";

			// 下载成功
			if(res->status == 200){
				// 获取正文长度
				std::string len = res->get_header_value("Content-Length");

				// 定义一个流,将字符串转为数字
				std::stringstream temp;
				temp << len;
				size_t fsize;
				temp >> fsize;

				// 下载路径
				std::string path = "./Download_Dir/";
				path += _file_list[choose];

				// 打开文件
				std::fstream fs(path.c_str(), std::ios::out | std::ios::binary);

				// 将响应正文写入文件
				fs.write(res->body.c_str(), fsize);

				fs.close();

				// 下载成功
				std::cout << "File " << _file_list[choose] << " Download Success!\n";
			}

			return true;
		}

	private:
		// 主机在主机列表中的下标
		int _host_idx;

		// 保存获取到的主机列表
		std::vector<std::string> _host_list;

		// 保存在线主机列表
		std::vector<std::string> _online_list;

		// 保存获取到的文件列表
		std::vector<std::string> _file_list;
};

int main(){
	// LFS客户端对象
	LFSClient _lfs;

	// 启动客户端
	_lfs.Start();

	return 0;
}

makefile文件如下

all:client server

client:client.cc
	g++ $^ -std=c++0x -lpthread -o $@ -lboost_system -lboost_filesystem
server:server.cc
	g++ $^ -std=c++0x -lpthread -o $@ -lboost_system -lboost_filesystem

.PHONY:clean
clean:
	rm server
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值