一.项目介绍:
该项目是一个局域网中进行附近文件共享下载的工具;实现能够进行搜索匹配局域网中运行工具的主机获取到局域网中在线主机列表,然后获取指定主机所共享的文件列表信息,并且对指定主机的指定文件进行多进程分块下载提高传输效率;
实质:搜索到局域网中的在线用户,获取到在线用户的列表后查看指定用户的共享文件列表,然后对感兴趣的文件进行选择下载;
二.整体的项目框架:
服务器端设计:
设计实现http服务端程序,能够提供浏览器客户端进行文件的下载,获取文件列表功能
客户端设计:
实现基于服务器HTTP的分块传输功能实现多进程文件分块下载功能的下载器,通过分块传输提高传输效率
三.实现流程:
- 发现局域网附近共享用户
- 列出附近用户列表,并选择想要查看的用户主机
- 获取指定用户的文件列表,并选择想要下载的文件
- 获取文件的头信息,主要获取文件长度
- 对获取到的长度进行下载分块区域划分
- 创建多进程进行分块下载文
四.主要的接口设计:
1.服务端功能接口:
- 提供客户端的主机配对功能;
- 提供客户端的文件列表获取功能;
- 提供客户端的文件下载功能;
/* 客户端配对请求
请求: GET /hostpair HTTP/1.1 /hostpair
响应: HTTP/1.1 200 OK
客户端文件列表获取请求
请求: GET /list/ HTTP/1.1 /(list/){0,1}
响应:
HTTP/1.1 200 OK
Content-Length: len
filename1\n
filename2\n
...
客户端文件分块下载请求
请求:
GET /list/filename HTTP/1.1 /list/(.*)
Range: byte=range_start-range_end
响应:
HTTP/1.1 200 OK
Content-Length: range_size
range_data
*/
#define SHARED_DIR "shared"
class P2PServer {
private:
httplib::Server srv;
public:
/*附近主机配对请求处理*/
static void PairHandler(const Request& req, Response& res);
/*文件列表请求处理*/
static void ListHandler(const Request& req, Response& res);
/*文件下载请求处理*/
static void DownloadHandler(const Request& req, Response& res);
};
2.客户端功能接口:
- 提供能够发现匹配局域网附近主机的功能;
- 提供能够获取指定主机共享文件列表功能;
- 提供能够下载指定主机下指定的共享文件功能;
- 能够进行客户端的功能显示;
#define DOWNLOAD_DIR "download"
#define RANGE_SIZE (10 << 20)
class P2pClient {
private:
std::vector<std::string> _host_list;
std::vector<std::string> _file_list;
private:
bool GetHostList(std::vector<std::string> &list);
int64_t GetFileSize(int file_id);
public:
int SelectShow(); //用户选择显示
bool PairNearbyHost(); //附近主机匹配
bool ShowNearByHost(); //显示附近主机
bool GetShareList(int host_id); //获取指定主机的共享文件列表
bool ShowShareList(); //显示指定主机的共享文件列表
bool DownLoadFile(int file_id); //下载指定主机的共享文件
};
3.其他接口的使用:
httplib基本使用:
/*test.cpp*/
#include "httplib.h"
static void HelloWorld(const httplib::Request &req, httplib::Response &rsp) {
rsp.set_content("<html>hello world</html>", "text/html");
return;
}
int main() {
httplib::Server srv;
srv.set_base_dir("./www");
srv.Get("/", HelloWorld);
srv.listen("0.0.0.0", 9000);
}
/*g++ -std=c++0x test.cpp -o test -lpthread -lboost_filesystem -lboost_system*/
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). */
};
五.代码实现原理:
关于服务端基本功能的实现:
Server.hpp
//实现服务端的基本功能:
//1.搭建http服务器;
//2.实现主机配对的功能,主要用于局域网的主机搜索配对功能;
//3.实现文件列表的获取,主要是向客户端提供一个文件列表,让用户来选择;
//4.响应文件的数据下载功能;
#ifndef __M_SRV_H__
#define __M_SRV_H__
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <boost/filesystem.hpp>
#include "httplib.h"
using namespace httplib;
namespace bf=boost::filesystem;
#define SHARED_PATH "Download"
#define LOG(fmt,...) fprintf(stderr,fmt,__VA_ARGS__)
class P2PServer{
private:
Server _server;
private:
static void GetHostPair(const Request &req,Response &rsp){ //主机配对
rsp.status=200;
}
static void GetFileList(const Request &req,Response &rsp){ //获取文件列表
bf::directory_iterator item_begin(SHARED_PATH);
bf::directory_iterator item_end;
std::stringstream body;
for(;item_begin!=item_end;++item_begin){
if(bf::is_directory(item_begin->status())){ //如果是一个目录就跳过;
continue;
}
bf::path path=item_begin->path();
std::string name = path.filename().string();
rsp.body += name + "\n";
}
rsp.set_header("Content-Type","text/html");
rsp.status=200;
}
static void GetFileData(const Request &req,Response &rsp){ //获取文件数据
bf::path path(req.path);
std::stringstream name;
name << SHARED_PATH<<"/" << path.filename().string();
if(!bf::exists(name.str())){
rsp.status=404;
return;
}
if(bf::is_directory(name.str())){
rsp.status=403;
return;
}
int64_t fsize=bf::file_size(name.str());
if(req.method=="HEAD"){
rsp.status=200;
std::string len=std::to_string(fsize);
rsp.set_header("Content-Length",len.c_str());
return;
}
else{
if(!req.has_header("Range")){
rsp.status=400;
return;
}
std::string range_val;
range_val=req.get_header_value("range");
int64_t start,rlen;
//bytes=start-end;
//
bool ret=RangeParse(range_val,start,rlen);
if(ret==false){
rsp.status=400;
return;
}
std::cerr<<"range:"<<range_val<<"\n";
std::cerr<<"body.resize:"<<rlen<<"\n";
rsp.body.resize(rlen);
std::ifstream file(name.str(),std::ios::binary);
if(!file.is_open()){
std::cerr<<"open file"<<name.str()<<"failed\n";
rsp.status=404;
return;
}
std::cerr<<"range:"<<range_val<<"\n";
std::cerr<<"body.resize:"<<rlen<<"\n";
rsp.body.resize(rlen);
std::ifstream file(name.str(),std::ios::binary);
if(!file.is_open()){
std::cerr<<"open file"<<name.str()<<"failed\n";
rsp.status=404;
return;
}
file.seekg(start,std::ios::beg);
file.read(&rsp.body[0],rlen);
if(!file.good()){
std::cerr<<"read file"<<name.str()<<"body error\n";
rsp.status=500;
return;
}
file.close();
rsp.status=206;
rsp.set_header("Content-Type","application/octet-stream");
std::cerr<<"file range:"<<range_val<<"download success\n";
}
}
static bool RangeParse(std::string &range_val,int64_t &start,int64_t &len){ //响应文件的数据下载功能;
size_t pos1=range_val.find("=");
size_t pos2=range_val.find("-");
if(pos1==std::string::npos || pos2==std::string::npos){
std::cerr<<"range"<<range_val<<"format error\n";
rsp.status=400;
return false;
}
int64_t end;
std::string rstart;
std::string rend;
rstart=range_val.substr(pos1+1,pos2-pos1-1);
rend=range_val.substr(pos2+1);
std::stringstream tmp;
tmp<<rstart;
tmp>>start;
tmp.clear();
tmp<<rend;
tmp>>end;
len=end-start+1;
return true;
}
public:
P2PServer(){
//判断共享目录若不存在,则创建;
if(bf::exists(SHARED_PATH)){
bf::create_directory(SHARED_PATH);
}
}
bool Start(uint16_t port){
_server.Get("/hostpair",GetHostPair);
_server.Get("/list",GetFileList);
_server.Get("list/(.*)",GetFileData);
_server.listen("0.0.0.0",port); //搭建服务器
} };
#endif
关于客户端的代码实现:
Client.hpp
//实现客户端的功能:
//1.获取局域网中所有主机的IP地址;
//2.获取在线主机列表(逐个向主机发送配对请求,判断响应状态;
//3.打印在线主机列表,并且向用户选择想要查看的主机共享文件列表;
//4.向选择的主机发送文件列表请求,获取到文件列表
//5.打印文件列表,并且用户选择想要下载的文件;
//6.下载文件(向指定的文件发送指定的文件下载请求)
#ifndef __M_CLI_H__
#define __M_CLI_H__
#include <iostream>
#include <thread>
#include <string>
#include <stdio.h>
#include <fstream>
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <boost/thread.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include "httplib.h"
#define RANGE_SIZE (1024*1024*100)
using namespace httplib;
namespace bf=boost::filesystem;
class P2PClient{
private:
uint16_t _srv_port;
int _host_idx; //用户选中的IP地址;
std::vector<std::string> _online_list; //在线主机列表
td::vector<std::string> _file_list; //文件列表
private: //私有接口;
bool GetAllHost(std::vector<std::string> &list){ //获取局域网中所有的主机列表;
struct ifaddrs *addrs=NULL;
struct sockaddr_in *ip=NULL;
struct sockaddr_in *mask=NULL;
getifaddrs(&addrs); //获取当前主机的网卡信息;
for(;addrs!=NULL;addrs=addrs->ifa_next){
ip=(struct sockaddr_in*)addrs->ifa_addr; //接口地址
mask=(struct sockaddr_in*)addrs->ifa_netmask; //接口的网络掩码
if(ip->sin_family!=AF_INET){
continue;
}
if(ip->sin_addr.s_addr==inet_addr("127.0.0.1")){
continue;
}
uint32_t net,host;
net=ntohl(ip->sin_addr.s_addr & mask->sin_addr.s_addr);
host=ntohl(~mask->sin_addr.s_addr);
for(int i=128;i<host;++i){
struct in_addr ip;
ip.s_addr=htonl(net+i);
list.push_back(inet_ntoa(ip));
}
}
freeifaddrs(addrs); //返回的数据是动态分配的,不需要时进行释放;
return true;
}
void HostPair(std::string &i){
Client client(i.c_str(),_srv_port);
auto rsp=client.Get("/hostpair");
if(rsp && rsp->status==200){
std::cerr<<"host"<<i<<"pair success\n";
_online_list.push_back(i);
}
fprintf(stderr,"%s\n",i.c_str());
return;
}
bool GetOnlineHost(std::vector<std::string> &list){ //进行主机配对
_online_list.clear();
std::vector<std::thread> thr_list(list.size());
for(int i=0;i<list.size();i++){
std::thread thr(&P2PClient::HostPair,this,&list[i]);
thr_list[i]=std::move(thr);
}
for(int i=0;i<thr_list.size();i++){
thr_list[i].join();
}
return true;
}
bool ShowOnlineHost(){ //显示在线列表
for(int i=0;i< _online_list.size();i++){
std::cout<<i<<". "<< _online_list[i] <<"\n";
}
std::cout<<"please choose:";
fflush(stdout);
std::cin>>_host_idx;
if(_host_idx<0 || _host_idx > _online_list.size()){
_host_idx=-1;
std::cerr<<"choose error\n";
return false;
}
return true;
}
bool GetFileList(){ //请求文件列表的获取
Client client(_online_list[_host_idx].c_str(),_srv_port);
auto rsp=client.Get("/list");
if(rsp && rsp->status==200){
boost::split(_file_list,rsp->body,boost::is_any_of("\n"));
}
return true;
}
bool ShowOnlineHost(){ //显示在线列表
for(int i=0;i< _online_list.size();i++){
std::cout<<i<<". "<< _online_list[i] <<"\n";
}
std::cout<<"please choose:";
fflush(stdout);
std::cin>>_host_idx;
if(_host_idx<0 || _host_idx > _online_list.size()){
_host_idx=-1;
std::cerr<<"choose error\n";
return false;
}
return true;
}
bool GetFileList(){ //请求文件列表的获取
Client client(_online_list[_host_idx].c_str(),_srv_port);
auto rsp=client.Get("/list");
if(rsp && rsp->status==200){
boost::split(_file_list,rsp->body,boost::is_any_of("\n"));
}
return true;
}
void RangeDownload(std::string host,std::string name,int64_t start,int64_t end,int *res){
std::string uri="/list/"+name;
std::string realpath="Download/"+name;
std::stringstream range_val;
range_val << "bytes=="<<start<<"-"<<end;
std::cerr<<"download range:"<<range_val.str()<<"\n";
//文件的分块下载;
//
*res=0;
Client client(host.c_str(),_srv_port);
Headers header;
header.insert(std::make_pair("Range",range_val.str().c_str()));
auto rsp=client.Get(uri.c_str(),header);
if(rsp && rsp->status==206){
int fd=open(realpath.c_str(),O_CREAT|O_WRONLY,0664);
if(fd<0){
std::cerr<<"file"<<realpath<<"open error\n";
return;
}
lseek(fd,start,SEEK_SET);
int ret=write(fd,&rsp->body[0],rsp->body.size());
if(ret<0){
std::cerr<<"file"<<realpath<<"write error\n";
close(fd);
return;
}
close(fd);
*res=1;
std::cerr << realpath << "download range:" ;
std::cerr<<range_val.str()<<"success\n";
}
return;
}
int64_t GetFileSize(std::string &host,std::string &name){ //获取文件的长度;
int64_t fsize=-1;
std::string path="/list/"+name;
Client client(host.c_str(),_srv_port);
auto rsp=client.Head(path.c_str());
if(rsp && rsp->status==200){
if(!rsp->has_header("Content-Length")){
return -1;
}
std::string len=rsp->get_header_value("Content-Length");
std::stringstream tmp;
tmp<<len;
tmp>>fsize;
}
return fsize;
}
bool DownloadFile(std::string &name){ //下载文件
//GET /list/filename Http/1.1
//Http/1.1 200 OK
//Content-Length:fsize
//
//FILE
//1.获取文件的总长度
//2.根据文件总长度和分块大小进行分割线程的下载区域
//3.创建线程下载指定文件的指定分块数据
//4.同步等待所有线程结束,获取下载结果;
std::string host=_online_list[_host_idx];
int64_t fsize=GetFileSize(host,name);
if(fsize<0){
std::cerr<<"download file "<<name<<"failed\n";
return false;
}
int count=fsize / RANGE_SIZE; //分块的个数;
std::cout<<"file size:"<<fsize<<"\n"; //打印一下文件的大小;
std::cout<<"range count:"<<count<<"\n"; //打印一下分块的个数;
std::vector<boost::thread> thr_list(count+1);
std::vector<int> res_list(count+1);
for(int64_t i=0;i<=count;i++){
int64_t start,end,rlen;
start=i * ((int64_t)RANGE_SIZE);
end=(i+1)*RANGE_SIZE-1;
if(i==count){
if(fsize % RANGE_SIZE==0){
break;
}
end=fsize-1;
}
std::cerr<<"range:"<<start<<"-"<<end<<"\n";
rlen=end-start+1;
//Range:bytes=start-end;
int *res=&res_list[i];
boost::thread thr(&P2PClient::RangeDownload,this,host,name,start,res);
thr_list[i]=std::move(thr);
}
int ret=true;
for(int i=0;i<=count;i++){
if(fsize % RANGE_SIZE==0 && i==count){
break;
}
thr_list[i].join();
if(res_list[i]==0){
std::cerr<<" range "<< i << "download failed\n"; //检查哪一块出错了
ret=false;
}
}
if(ret==true){
std::cerr<<"download file"<<name<<"success\n";
return true;
}
else{
std::cerr<<"dowmload file"<<name<<"failed\n";
return false;
}
return true;
}
int DoFace(){
std::cout<<"0.退出\n";
std::cout<<"1.搜索附近主机\n";
std::cout<<"2.显示在线主机\n";
std::cout<<"3.显示文件列表\n";
int choose;
std::cout<<"please choose: ";
fflush(stdout);
std::cin>>choose;
return choose;
}
public:
P2PClient(uint16_t port)
:_srv_port(port)
{}
bool Start(){
while(1){
int choose=DoFace();
std::vector<std::string> list;
switch(choose){
case 0:
exit(0);
default:
break;
case 1:
GetAllHost(list);
GetOnlineHost(list);
break;
case 2:
if(!ShowOnlineHost()){
break;
}
GetFileList();
break;
case 3:
std::string filename;
if(ShowFileList(filename)==false){
break;
}
DownloadFile(filename);
break;
}
}
}
};
#endif
将代码进行封装:main.cpp
#include "Client.hpp "
#include "Server.hpp "
void srv_start(){
P2PServer server;
server.Start(9000);
}
int main(){
std::thread thr(srv_start);
thr.detach();
P2PClient client(9000);
client.Start;
return 0;
}