小项目---基于局域网的P2P文件共享工具

项目名称:P2P文件共享神器


项目介绍:

该项目完成了一个基于本地局域网的P2P文件共享小工具。本项目分为客户端与服务端。以下是功能介绍:
  1. 能够进行搜索匹配局域网中运行此工具的在线主机;获取到局域网在线主机列表,并进行多线程主机配对;
  2. 能够获取指定主机所共享的文件信息列表(指定的共享目录下的文件信息);
  3. 能够对指定主机上的指定文件进行多线程分块下载来提高传输效。

使用到的库

本项目基于HTTPLIB库,以及使用了BOOST库中的文件系统与BOOST线程库还有BOOST库中的算法库;在获取局域网内IP地址是用的是

在这里插入图片描述
服务端功能接口

  1. 提供客户端的主机配对功能
  2. 提供客户端的文件列表获取功能
  3. 提供客户端的文件下载功能
#define SHARD_PATH "SHARD"
class P2PServer{
private:
    // 实例化一个服务端
    Server _server;
private:
	// 主机配对
    static void GetHostPair(const Request& req,Response& rsp);
    // 文件列表信息
    static void GetFileList(const Request& req,Response& rsp);
    // 获取文件数据
    static void GetFileData(const Request& req,Response& rsp);
    // 分块响应数据
    static bool RangeParse(std::string &range_val, int64_t &start, \
                    int64_t &len);
public:
    P2PServer(){
      if(!bf::exists(SHARD_PATH)){
          bf::create_directory(SHARD_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);
    }
};

客户端功能接口

  1. 提供能够发现匹配局域网附近主机功能
  2. 提供能够获取指定主机共享文件列表功能
  3. 提供能够下载指定主机下指定的共享文件功能
class P2PClient{

private:
    uint16_t _srv_port;
    int _host_idx;
    std::vector<std::string> _online_list;
    std::vector<std::string> _file_list;
private:
	// 获取局域网内所有IP地址
    bool GetAllHost(std::vector<std::string> &list);
	// 获取局域网内所有IP地址线程入口函数
    void HostPair(std::string &i);
	// 获取在线主机列表
    bool GetOnlineHost(std::vector<std::string> &list);
    // 进行主机配对
    bool ShowOnlineHost();
	// 获取文件列表信息
    bool GetFileList();
    // 选择下载文件列表
    bool ShowFileList(std::string &name);
    // 分块请求下载线程入口函数
    void RangeDownLoad(std::string host, std::string name, 
                       int64_t start, int64_t end, int *res);
    // 获取文件大小
    int64_t GetFileSize(std::string &host, std::string &name);
	// 下载文件
    bool DownLoadFile(std::string &name);
	// 用户界面
    int DoFace(){
        std::cout << "0. 退出\n";
        std::cout << "1. 搜索附近主机\n";
        std::cout << "2. 显示在线主机\n";
        std::cout << "3. 显示文件列表\n";
        int choose = 0;
        std::cout << "please choose:";
        fflush(stdout);
        std::cin >> choose;
        return choose;
    }
public:
    P2PClient(uint16_t port):_srv_port(port){}
    bool Start();
};

接口熟悉:

1. 使用httplib库
#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); 
	return 0;
}
// g++ -std=c++0x test.cpp -o test -lpthread -lboost_filesystem -lboost_system

服务端源代码:

#include<iostream>
#include<unistd.h>
#include<boost/filesystem.hpp>
#include<stdio.h>
#include "httplib.h"

#define SHARD_PATH "SHARD"
#define LOG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)

using namespace httplib;
namespace bf = boost::filesystem;

class P2PServer
{
  private:
    Server _server;
  private:
    static void GetHostPair(const Request& req,Response& rsp)
    {
      rsp.status =200;
      return;
    }
    static void GetFileList(const Request& req,Response& rsp)
    {
      bf::directory_iterator it_begin(SHARD_PATH);
      bf::directory_iterator it_end;
      std::stringstream body;
     // body<<"<html><body>";
      for(;it_begin!=it_end;it_begin++){
        if(bf::is_directory(it_begin->status())){
            continue;
        }
        std::string name = it_begin->path().filename().string();
        rsp.body+=name+"\n";
       // body << "<h4><a href='/list/"<< name <<"'>";
       // body << name;
       // body <<"</a></h4>";
       //rsp.body+=name;
      }
     // body<<"</body></html>";
     // rsp.body = body.str();
      rsp.set_header("Content-Type","text/html");//渲染
      rsp.status = 200;
    }

    static void GetFileData(const Request& req,Response& rsp)
    {
      //a.txt->Dowanload/a.txt
      bf::path path(req.path); //生成一个path对象
      std::stringstream name;
      name<< SHARD_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;
          
          bool ret = RangeParse(range_val, start, rlen);
          if(ret == false){
            rsp.status = 400;
            return;
          }

          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); //写到body里了
          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;
          std::cerr << "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";
            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(SHARD_PATH)){
          bf::create_directory(SHARD_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);
    }
};

int main()
{
  P2PServer srv;
  srv.Start(9000);
  return 0;
}

客户端:

#include <iostream>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
#include <fstream>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "httplib.h"
#include <thread>

#define RANGE_SIZE (1024*1024*10)   // 10M
using namespace httplib;
namespace bf = boost::filesystem;

class P2PClient{

private:
    uint16_t _srv_port;
    int _host_idx;
    std::vector<std::string> _online_list;
    std::vector<std::string> _file_list;
private:
    bool GetAllHost(std::vector<std::string> &list){

        struct sockaddr_in *ip = NULL;
        struct sockaddr_in *mask = NULL;
        struct ifaddrs *addrs = 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 = 2; i < host-1; 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 req = client.Get("/hostpair");
        if(req && req->status == 200){
            std::cerr << "host " << i << " pair success\n";
            _online_list.push_back(i);
        }
        std::cerr << "host " << i << " pair failed\n";

        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, std::ref(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()){
            std::cerr << "choose error\n";
            return false;
        }
        return true;
    }

    bool GetFileList(){
        Client client(_online_list[_host_idx].c_str(), _srv_port);
        auto req = client.Get("/list");
        if(req && req->status == 200){
            //std::vector<std::string> list;
            boost::split(_file_list, req->body, boost::is_any_of("\n"));
        }
        return true;
    }

    bool ShowFileList(std::string &name){
        for(int i = 0; i < _file_list.size(); i++){
            std::cout << i << ". " << _file_list[i] << "\n";
        }
        std::cout << "please choose:";
        fflush(stdout);
        int file_idx;
        std::cin >> file_idx;
        if(file_idx < 0 || file_idx > _file_list.size()){
            std::cerr << "choose error\n";
            return false;
        }
        name = _file_list[file_idx];
        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 req = client.Get(uri.c_str(), header);
        if(req && req->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, &req->body[0], req->body.size());
            if(ret < 0){
                std::cerr << "file " << realpath << "write error\n";
                close(fd);
                return;
            }
            close(fd);
            *res = 1;
            std::cerr << "file " << 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 req = client.Head(path.c_str());
        if(req && req->status == 200){
            if(!req->has_header("Content-Length")){
                return -1;
            }
            std::string len = req->get_header_value("Content-Length");
            std::stringstream tmp;
            tmp << len;
            tmp >> fsize;
        }
        return fsize;
    }

    bool DownLoadFile(std::string &name){
        // GET /list/filename
        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 << "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, len;
            start = i * 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";
            len = end - start + 1;
            
            int *res = &res_list[i];
            boost::thread thr(&P2PClient::RangeDownLoad, this, host, name, start, end, res);
            thr_list[i] = std::move(thr);
        }

        bool ret = true;
        for(int i = 0; i <= count; i++){
            if(i == count && fsize % RANGE_SIZE == 0){
                break;
            }
            thr_list[i].join();
            if(res_list[i] == 0){
                ret = false;
            }
        }

        if(ret == true){
            std::cerr << "download file " << name << " success\n";
        }
        else{
            std::cerr << "download 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 = 0;
        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::string filename;
            std::vector<std::string> list;
            switch(choose){
            case 1:
                GetAllHost(list);
                GetOnlineHost(list);
                break;
            case 2:
                if(ShowOnlineHost() == false){
                    break;
                }
                GetFileList();
                break;
            case 3:
                if(ShowFileList(filename) == false){
                    break;
                }
                DownLoadFile(filename);
                break;
            case 0:
                exit(0);
            default:
                break;
            }
        }
    }

};

int main(){

    P2PClient cli(9000);
    cli.Start();
    return 0;
}

makefile:

all:server client
client:client.cpp
	g++ -std=c++0x $^ -o $@ -lpthread -lboost_filesystem -lboost_system -lboost_thread                                                                                
server:server.cpp
	g++ -std=c++0x $^ -o $@ -lpthread -lboost_filesystem -lboost_system
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值