项目名称:P2P文件共享神器
项目介绍:
该项目完成了一个基于本地局域网的P2P文件共享小工具。本项目分为客户端与服务端。以下是功能介绍:- 能够进行搜索匹配局域网中运行此工具的在线主机;获取到局域网在线主机列表,并进行多线程主机配对;
- 能够获取指定主机所共享的文件信息列表(指定的共享目录下的文件信息);
- 能够对指定主机上的指定文件进行多线程分块下载来提高传输效。
使用到的库
本项目基于HTTPLIB库,以及使用了BOOST库中的文件系统与BOOST线程库还有BOOST库中的算法库;在获取局域网内IP地址是用的是
服务端功能接口
- 提供客户端的主机配对功能
- 提供客户端的文件列表获取功能
- 提供客户端的文件下载功能
#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);
}
};
客户端功能接口
- 提供能够发现匹配局域网附近主机功能
- 提供能够获取指定主机共享文件列表功能
- 提供能够下载指定主机下指定的共享文件功能
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