本篇博客主要实现客户端主要功能,如:获取局域网在线主机列表、获取指定主机的共享文件列表、下载指定主机的指定文件。
客户端请求示例
我们首先来实现一个简单的客户端请求,首先我们来测试一下对服务器发送一个主机配对请求:
#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;
}
首先,我们先启动服务器:
然后,编译运行该客户端请求:
可以看到,主机配对成功。
客户端功能实现
我们的客户端一共有以下三个功能:
- 发现局域网主机,进行主机配对;
- 获取指定主机的共享文件列表;
- 下载指定主机的指定文件;
首先,我们先来写一个整体代码框架:
#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