项目介绍:
该项目完一个在局域网中进行附近文件共享下载功能的工具。
- 能够进行搜索匹配局域网中运行工具的主机,获取到局域网在线的主机列表
- 能够获取在线主机所共享的文件信息列表
- 能够对指定主机上的文件进行多进程分块下载来提高传输效率
P2P下载器功能流程:
- 局域网中的主机发现 找到局域网中有哪儿些主机在使用P2P下载器
- 看一下附近的主机,有哪儿些文件时共享的
- 将这个主机上的共享文件下载下来
客户端与服务端进行数据通信:
1.通信协议的选择:HTTP超文本传输协议
HTTP协议格式
首行:
请求首行:请求方法 GET url 协议版本 1.1 \r\n
响应首行:协议版本 响应状态码 状态码描述信息\r\n
头部:以 key-value 的形式组成一个键值对,并且键值对之间以\r\n进行间隔
空行:
正文:
处理细节:
1.一个主机如何发现附近的主机--下载器之间的通信
一个主机将一个主机的配对需求,发送到局域网中的所有主机上,这时候如果有主机运行了P2P下载器程序,则对这个请求,进行一个配对回应。
2.查看附近主机的共享文件
向指定的主机发送一个获取文件列表的请求
附近主机收到请求后,则将共享的目录下的所有文件名相应出去
3.下载指定文件
向指定的主机发送获取文件的请求
指定主机收到请求后,则打开指定文件
服务器端设计:
设计实现HTTP服务端程序,能够提供浏览器客户端进行文件的下载,获取文件列表功能
服务端流程:
1.搭建HTTP服务器
1.主机配对请求处理功能
2.主机文件列表获取处理功能
3.主机获取数据获取功能
2.能够提供附近主机配对功能
3.能够像附近主机提供文件列表
4.能够向附近主机提供文件下载功能
客户端设计
实现基于服务器HTTP的分块传输功能实现多进程文件分块下载功能的下载器,通过分块传输提高传输效率
客户端流程:
1.获取局域网中所有的IP地址信息
2.向获取到的IP主机地址发送主机配对请求--获取到配对成功的主机IP地址列表
3.打印配对成功的主机列表
4.用户选择想要获取哪儿个主机的共享文件
5.向指定的这个主机发送文件列表获取请求 -- 获取到主机上的共享文件列表
6.打印所有的文件列表,用户选择想要下载哪儿个共享文件
7.向指定的主机发送文件数据获取请求
实现流程:
- 发现局域网附近的共享用户
23 //1. 获取局域网IP地址列表 24 bool GetHostList(std::vector<std::string> &list) { 25 struct ifaddrs *addrs; 26 getifaddrs(&addrs); 27 while (addrs) { 28 if (strcmp(addrs->ifa_name, "lo") == 0) { 29 addrs = addrs->ifa_next; 30 continue; 31 } 32 sockaddr_in *addr=(sockaddr_in*)addrs->ifa_addr; 33 sockaddr_in *mask=(sockaddr_in*)addrs->ifa_netmask; 34 if (addr->sin_family != AF_INET) { 35 addrs = addrs->ifa_next; 36 continue; 37 } 38 uint32_t net = ntohl((addr->sin_addr.s_addr & mask->sin_addr.s_addr)) ; 39 int host = ntohl(~mask->sin_addr.s_addr); 40 41 for (int i = 1; i < host; i++) { 42 struct in_addr ip; 43 ip.s_addr = htonl(net + i); 44 list.push_back(inet_ntoa(ip)); 45 } 46 addrs = addrs->ifa_next; 47 } 48 return true; 49 }
-
列出附近用户列表,并选择想要查看的用户主机
//打印配对成功的主机列表 67 void PrintHost() { 68 for (int i = 0; i < _host_list.size(); i++) { 69 std::cout <<i<<". [" << _host_list[i] << "]\n"; 70 } 71 SelectHost(); 72 } 73 //用户选择要获取哪个主机的文件列表 74 void SelectHost() { 75 std::cout <<"选择想要查看的主机:"; 76 fflush(stdout); 77 std::cin >> _host_idx; 78 GetFileList(_host_list[_host_idx]); 79 }
- 获取指定用户的文件列表,并选择想要下载的文件
80 //3.获取指定主机的文件列表 81 bool GetFileList(std::string &host_addr) { 82 httplib::Client client(host_addr.c_str(), 9000); 83 auto rsp = client.Get("/list"); 84 if (rsp && rsp->status == 200) { 85 //filename1\nfilename2... 86 boost::split(_file_list, rsp->body, boost::is_any_of("\n")); 87 }else { 88 std::cerr<<"host:["<<host_addr<<"] get file list failed\n"; 89 } 90 return true; 91 } 92 //4.打印文件列表 93 void PrintFile() { 94 for (int i = 0; i < _file_list.size(); i++) { 95 std::cout << i << ". ["<<_file_list[i]<<"]\n"; 96 } 97 SelectFile(); 98 }
- 获取文件的头信息(主要是获取文件的长度信息)
84 void GetFileData(const httplib::Request &req, httplib::Response &rsp){ 85 // /list/abc.txt -> root/list/abc.txt 86 87 std::cerr << "download file:["<<req.path<<"]\n"; 88 std::string realpath = ROOT_PATH + req.path; 89 if (!bf::exists(realpath)) { 90 std::cerr << "file:["<<realpath<<"] is not exists\n"; 91 rsp.status = 404; 92 return; 93 } 94 //bf::file_size() 获取文件大小 95 int64_t fsize = bf::file_size(realpath); 96 97 std::ifstream file(realpath, std::ios::binary); 98 if (!file.is_open()) { 99 rsp.status = 500; 100 return ; 101 } 102 rsp.body.resize(fsize); 103 file.read(&rsp.body[0], fsize); 104 if (!file.good()) { 105 rsp.status = 500; 106 return; 107 } 108 file.close(); 109 110 rsp.set_header("Content-Type", "application/octet-stream"); 111 rsp.status = 200; 112 return ; 113 }
- 对获取到的文件长度进行分区域划分下载
99 //选择文件进行下载 100 bool SelectFile() { 101 std::cout<<"选择想要下载文件id:"; 102 fflush(stdout); 103 std::cin >> _file_idx; 104 DownLoadFile(_file_list[_file_idx]); 105 return true; 106 } 107 bool DownLoadFile(std::string &filename) { 108 std::string host_addr = _host_list[_host_idx]; 109 httplib::Client client(host_addr.c_str(), 9000); 110 auto rsp = client.Get(filename.c_str()); 111 if (rsp && rsp->status == 200) { 112 // /list/filename -> filename 113 // ./download/ + filename -> ./download/filename 114 boost::filesystem::path path(filename); 115 std::string file = path.filename().string(); 116 std::string realpath = _download_path + file; 117 std::ofstream fs(realpath, std::ios::binary); 118 if (!fs.is_open()) { 119 std::cerr << "open file:["<<realpath<<"] failed\n"; 120 return false; 121 } 122 fs.write(&rsp->body[0], rsp->body.size()); 123 if (!fs.good()) { 124 std::cerr << "write file:["<<realpath<<"] failed\n"; 125 return false; 126 } 127 fs.close(); 128 std::cerr << "download file success\n"; 129 }else { 130 std::cerr << "download file failed!\n"; 131 return false; 132 } 133 return true; 134 }
- 创建多进程进行分块文件下载
主要功能的接口设计
服务器功能接口:
- 提供客户端的主机配对功能
void GetHostPair(const httplib::Request &req, httplib::Response &rsp)
-
提供客户端的文件列表获取功能
void GetFileList(const httplib::Request &req, httplib::Response &rsp)
- 提供客户端的文件下载功能
void GetFileData(const httplib::Request &req, httplib::Response &rsp)
客户端功能接口:
- 提供能够发现匹配局域网附近主机的功能
bool GetHostList(std::vector<std::string> &list)
- 提供能够获取指定主机共享文件列表的功能
bool GetFileList(std::string &host_addr)
- 提供能够下载指定主机下指定共享文件的功能
bool DownLoadFile(std::string &filename)
其它接口包括httplib.h的基本使用
及ifaddr.c:
struct ifaddrs *addrs = NULL;
int getifaddrs(struct ifaddrs **ifap); /*获取本机网卡信息*/
返回值:0-成功 -1-失败
void freeifaddrs(struct ifaddrs *ifa); /*释放网卡信息存储资源*/
struct ifaddrs
{
struct ifaddrs *ifa_next; /* 链表指针,指向下一个网卡信息 */
char *ifa_name; /* 网卡名称*/
unsigned int ifa_flags; /* Flags as from SIOCGIFFLAGS ioctl. */
struct sockaddr *ifa_addr; /* 地址结构*/
struct sockaddr *ifa_netmask; /* 子网掩码*/
union
{
/* At most one of the following two is valid. If the IFF_BROADCAST
bit is set in `ifa_flags', then `ifa_broadaddr' is valid. If the
IFF_POINTOPOINT bit is set, then `ifa_dstaddr' is valid.
It is never the case that both these bits are set at once. */
struct sockaddr *ifu_broadaddr; /* Broadcast address of this interface. */
struct sockaddr *ifu_dstaddr;
/* Point-to-point destination address. */
} ifa_ifu;
# ifndef ifa_broadaddr
# define ifa_broadaddr ifa_ifu.ifu_broadaddr
# endif
# ifndef ifa_dstaddr
# define ifa_dstaddr ifa_ifu.ifu_dstaddr
# endif
void *ifa_data; /* Address-specific data (may be unused). */
};
但要注意的是如果是在LINUX系统下进行的操作,那么就必须要关闭防火墙
su root
systemctl stop firewalld