3
3

More than 3 years have passed since last update.

P2P なぜなにTorrent 2021 用 ネタ集め(2) Libtorrent の使い方 01

Last updated at Posted at 2021-02-09

bittorrent の network に参加するプログラム を書く場合、多くの場合は何かしらのライブラリーを使うと思われます。

この記事は libtorrent 2.0 の使い方について解説します。

dev.to に投稿した Hello libtorrent

まで読んでいただけると、ドキュメント化されてない使い方やGoogleしても出てこない利用方法なども解説していますので、

libtorrent で こんな事もできるのかと
発見があると思います。

Code

以下で公開しています。
https://github.com/kyorohiro/hello_libtorrent

#DEVCommunity 向けに作成したものです
Hello libtorrent (from dev.to)

開発環境の構築は、前回の記事を参考にしてください

使い方

Bencode

Bittorrent の  プロトコルでは bencode を利用してデータをformat します。
感覚としては、バイナリデータを使えるjson という感じです。

main_bencode.cpp

//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bencode.cpp -ltorrent-rasterbar -lpthread
//
#include<iostream>
#include<libtorrent/bdecode.hpp>
#include<libtorrent/bencode.hpp>
#include<vector>
#include<stdio.h>



// [Memo]
// using dictionary_type = std::map<std::string, entry, aux::strview_less>;
// using string_type = std::string;
// using list_type = std::vector<entry>;
// using integer_type = std::int64_t;
// using preformatted_type = std::vector<char>;

int main() {
    //
    // encode
    //
    {
        // string 
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry("hello"));
        std::cout << s << std::endl; // 5:hello
    }
    {
        // number
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(12));
        std::cout << s << std::endl; // i12e
    }
    {
        // list
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(std::vector<lt::entry>{
            lt::entry("hello"),lt::entry(12)
        }));
        std::cout << s << std::endl; // l5:helloi12ee
    }
    {
        // dict
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(
           lt::entry::dictionary_type {
                {"num", lt::entry(12)},
                {"str", lt::entry("hello")}
            }
        ));
        std::cout << s << std::endl; // d3:numi12e3:str5:helloe
    }
    {
        // binary
        std::string s;
        lt::bencode(std::back_inserter(s), lt::entry(std::vector<char>{(char)0xFF,(char)0x01,(char)0x03}));
        std::cout << s << std::endl; // i-12e
    }
    {
        // other
        std::vector<char> output;
        lt::entry source_data;

        source_data["bstring"] = lt::entry::string_type("hello");
        source_data["bint"] = lt::entry::integer_type(12);
        source_data["elist"] = std::vector<lt::entry>{
            lt::entry(lt::entry::string_type("01")), lt::entry(lt::entry::integer_type(1))
        };
        source_data["tperformatted_type"] = lt::entry::preformatted_type{(char)0xFF,(char)0x01,(char)0x03};

        lt::bencode(std::back_inserter(output), source_data);
        std::cout << std::string(output.data(), output.size()) << std::endl;
    }

    return 0;
}
main_bdecode.cpp
//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bdecode.cpp -ltorrent-rasterbar -lpthread
//
#include<iostream>
#include<libtorrent/bencode.hpp>
#include<libtorrent/bdecode.hpp>

int main(int argc, char* argv[]) {
    {
        // deprecated? by TORRENT_DEPRECATED
        lt::entry result = lt::bdecode(std::string("i12e"));
        std::cout << result.integer() << std::endl; // 12
    }

    {
        // string 
        std::cout << "# string" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("5:hello", ec);
        std::cout << e.string_value() << std::endl; 
    }
    {
        // number
        std::cout << "# number" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("i12e", ec);
        std::cout << e.int_value() << std::endl; 
    }
    {
        // list
        std::cout << "# list" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("l5:helloi12ee", ec);

        std::cout << e.list_at(0).string_value() << std::endl; 
        std::cout << e.list_at(1).int_value() << std::endl; 
    }
    {
        // dict
        std::cout << "# dict" << std::endl; 
        lt::error_code ec;
        lt::bdecode_node e = lt::bdecode("d3:numi12e3:str5:helloe", ec);

        std::cout << e.dict_find("num").int_value() << std::endl; 
        std::cout << e.dict_find("str").string_value() << std::endl;
    }
    return 0;
}

Torrent file

Bittorrent では、ダウンロードしたい/アップロードしたいデータについての情報を、".torrent"にまとめます。

create_torrent.cpp

#include<iostream>
#include<vector>
#include<unistd.h>

#include<libtorrent/bencode.hpp>
#include<libtorrent/torrent_info.hpp>
#include<libtorrent/create_torrent.hpp>

std::string to_absolute_path(std::string args);
std::string extract_parent_path(std::string filepath);
void print_usage(std::string execute_file_name);

std::string tracker_address = "";
int main(int argc, char *argv[]) {
    std::string target_tracker_address = "";
    std::string target_file_path = "";

    //
    // extract tracker address and target file from argv
    // 
    std::vector<std::string> args;
    for(int i=1; i<argc;i++) {
        args.push_back(std::string(argv[i]));
    }

    if(args.size() <= 0) {
        print_usage(std::string(argv[0]));
        return -1;
    }

    for(auto v=args.begin(); v != args.end();v++){
        if(v->length() > 0 && v->rfind("-", 0) == 0){
            if((v+1) == args.end()){
                break;
            }
        }
        if(std::string("-a") == *v) {
            target_tracker_address = *(++v);
            continue; 
        }
        else {
            target_file_path = *v;
        }
    }

    //
    // setup file storage
    std::string source_absolute_path = to_absolute_path(target_file_path);
    lt::file_storage fs;
    lt::create_flags_t flags = {};
    lt::add_files(fs, source_absolute_path, flags);

    // 
    // create torrent creator
    lt::create_torrent torrent(fs, 16*1024);
    if(target_tracker_address.length() > 0) {
        torrent.add_tracker(target_tracker_address);
    }
    //
    // add hash info
    lt::set_piece_hashes(torrent, extract_parent_path(source_absolute_path));

    //
    // generate metadata
    lt::entry entry_info = torrent.generate();

    //
    // convert to bencoding
    std::vector<char> torrentfile_source;
    lt::bencode(std::back_inserter(torrentfile_source), entry_info); // metainfo -> binary
    std::cout.write(torrentfile_source.data(),torrentfile_source.size()); // binary -> stdout

    return 0;
}

void print_usage(std::string args0) {
    std::cerr << ""<< args0 << " <filename> [ -a <tracker address> ]" << std::endl;
}

std::string to_absolute_path(std::string path) {
    if(path.length() == 0) {
        throw std::runtime_error("failed to current_dir");
    }
    if(path[0] == '/') {
        return path;
    }
    char current_dir_path[2056];
    auto ret = getcwd(current_dir_path, sizeof(current_dir_path));
    if(ret == NULL) {
        std::cerr << "failed to current_dir ";
        throw std::runtime_error("failed to current_dir");
    }
    if(0==path.compare(0, 2, "./")) {
        path = path.substr(2);
    }
    return std::string(current_dir_path) + "/" + path;
} 

std::string extract_parent_path(std::string filepath)
{
  if (filepath.empty()) {
    return filepath;
  }
  int pos = filepath.find_last_of("/");

  return filepath.substr(0,pos
dump_torrent.cpp
//
//  LD_LIBRARY_PATH=/usr/local/lib/libtorrent-rasterbar.so g++ main_bdecode_dump.cpp  -o dumper.out -ltorrent-rasterbar -lpthread
//

#include <iostream>
#include <fstream>
#include <vector>
#include <iterator>
#include <libtorrent/bdecode.hpp>
#include <regex>
#include <string>

void print_usage(std::string args0);
lt::bdecode_node decode_file(std::string filepath);
void print_bencode_node(lt::bdecode_node target);
std::string to_asis_hex_string(std::string target, int max);

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        print_usage(std::string(argv[0]));
        return -1;
    }

    // decode to entry from a file
    std::ifstream input(std::string(argv[1]), std::ios::binary);
    std::vector<char> source((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
    lt::error_code ec;
    lt::bdecode_node node = lt::bdecode(source, ec);

    // lt::bdecode_node node = decode_file(std::string(argv[1]));
    // decode_file() is wrong, do Segmentation fault (core dumped)
    print_bencode_node(node);
    return 0;
}

void print_usage(std::string args0)
{
    std::cerr << "" << args0 << " <filename>" << std::endl;
}

lt::bdecode_node decode_file(std::string filepath)
{
    std::ifstream input(filepath, std::ios::binary);
    std::vector<char> source((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
    lt::error_code ec;
    lt::bdecode_node node = lt::bdecode(source, ec);
    return node;
}

// display all
enum item_type
{
    ipair,
    inode,
    ilistend,
    idictend
};

class item
{
public:
    std::pair<lt::string_view, lt::bdecode_node> pair;
    lt::bdecode_node node;
    item_type type;
    int depth;
    item(lt::bdecode_node node, int depth)
    {
        this->node = node;
        this->type = item_type::inode;
        this->depth = depth;
    }
    item(std::pair<lt::string_view, lt::bdecode_node> pair, int depth)
    {
        this->pair = pair;
        this->type = item_type::ipair;
        this->depth = depth;
    }
    item(item_type type, int depth)
    {
        this->type = type;
        this->depth = depth;
    }
};

void print_bencode_node(lt::bdecode_node target)
{
    std::vector<item> stack;
    stack.push_back(item(target, 1));

    while (stack.size() > 0)
    {
        item im = stack.back();
        stack.pop_back();

        std::string spaces = "";
        for (int i = 0; i < im.depth; i++)
        {
            spaces += "  ";
        }
        if (im.type == item_type::inode)
        {
            lt::bdecode_node node = im.node;
            if (node.type() == lt::bdecode_node::dict_t)
            {
                std::cout << std::endl
                          << spaces + "{";
                stack.push_back(item(item_type::idictend, im.depth));
                for (int i = 0; i < node.dict_size(); i++)
                {
                    stack.push_back(item(node.dict_at(node.dict_size() - 1 - i), im.depth + 1));
                }
            }
            else if (node.type() == lt::bdecode_node::list_t)
            {
                std::cout << "["
                          << "  ";
                stack.push_back(item(item_type::ilistend, im.depth));
                for (int i = 0; i < node.list_size(); i++)
                {
                    stack.push_back(item(node.list_at(node.dict_size() - 1 - i), im.depth + 1));
                }
            }
            else if (node.type() == lt::bdecode_node::int_t)
            {
                std::cout << "'" << node.int_value() << "'";
            }
            else if (node.type() == lt::bdecode_node::string_t)
            {
                std::cout << "'" << to_asis_hex_string(node.string_value().to_string(), 100) << "'";
            }
            else if (node.type() == lt::bdecode_node::none_t)
            {
                std::cout << "<none>";
            }
            else
            {
                std::cout << "<else>" << node.type() << std::endl;
            }
        }
        else if (im.type == item_type::ilistend)
        {
            std::cout << "  ]";
        }
        else if (im.type == item_type::idictend)
        {
            std::cout << std::endl
                      << spaces << "}";
        }
        else
        {
            std::pair<lt::string_view, lt::bdecode_node> pair = im.pair;
            std::cout << std::endl
                      << spaces << "<" << to_asis_hex_string(im.pair.first.to_string(), 100) << "> : "; // << std::endl;
            stack.push_back(item(pair.second, im.depth + 1));
        }
    }
    std::cout << std::endl;
}

std::string to_asis_hex_string(std::string target, int max)
{

    bool isAscii = true;
    for (int i = 0; i < target.size(); i++)
    {
        if (!(0 <= target[i] && target[i] < 127))
        {
            isAscii = false;
            break;
        }
    }
    if (isAscii)
    {
        // ascii
        return target.substr(0, max);
    }
    else
    {
        if (max > target.size() * 2)
        {
            max = target.size() * 2 + 1;
        }
        std::vector<char> ret(max);
        const char x[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        for (int i = 0; i < target.size() && i * 2 < max; i++)
        {
            int v = (unsigned char)target[i];
            ret[i * 2] = x[(0xf0 & v) >> 4];
            ret[i * 2 + 1] = x[(0x0f & v)];
        }
        return std::string("(size:") + std::to_string(target.size()) + std::string(")") + std::string(ret.begin(), ret.end());
    }
}

Torrentfile からデータを ダウンロード

※ Bittorrent の ネットワークではデータのダウンロードをする際に、同時にデーターのアップロードします。

tiny_cliet.cpp

#include<iostream>
#include<libtorrent/session.hpp>
#include<libtorrent/alert_types.hpp>
#include<libtorrent/alert.hpp>
#include<libtorrent/magnet_uri.hpp>
#include<libtorrent/error_code.hpp>

void print_usage(std::string);

std::string my_listen_interfaces = "";// "0.0.0.0:6881,[::]:6881"  "{E4F0B674-0DFC-48BB-98A5-2AA730BDB6D6}:7777"
std::string target_torrentfile_path = "";
std::string target_magnet_link = "";//magnet:?xt=urn...

int main(int argc, char *argv[]) try {

    if(argc > 1) {
        // 
        std::string tmp = std::string(argv[1]);
        if(tmp.rfind("magnet:",0) == 0) {
            target_magnet_link = tmp;
            target_torrentfile_path = "";
        } else {
            target_magnet_link = "";
            target_torrentfile_path = tmp;
        }
    }

    lt::settings_pack session_params;
    session_params.set_int(lt::settings_pack::alert_mask, lt::alert_category::all);
    if(my_listen_interfaces.length() != 0) {
        session_params.set_str(lt::settings_pack::listen_interfaces, my_listen_interfaces);
    }
    lt::session session(session_params);

    lt::add_torrent_params torrent_params;
    torrent_params.save_path = ".data"; // save in this dir
    if(target_torrentfile_path.length() != 0) {
        torrent_params.ti = std::make_shared<lt::torrent_info>(target_torrentfile_path);
    } else {
        lt::error_code ec;
        lt::parse_magnet_uri(target_magnet_link,torrent_params, ec);
        if (ec){
            std::cerr << "wrong magnet link "  << target_magnet_link << std::endl;
            throw std::invalid_argument("wrong magnet link \""+ target_magnet_link+"\"");
        }
        torrent_params = lt::parse_magnet_uri(target_magnet_link);
    }
    session.add_torrent(torrent_params);

    while (true)
    {
        std::vector<lt::alert *> alerts;
        session.pop_alerts(&alerts);

        lt::state_update_alert *st;
        for (lt::alert *a : alerts)
        {
            std::cout << "[" << a->type() << "](" << a->what() << ") " << a->message() << std::endl;
            switch (a->type())
            {
            case lt::state_update_alert::alert_type:
                //lt::state_update_alert
                st = (lt::state_update_alert *)(a);
                {
                    lt::torrent_status const &s = st->status[0];
                    std::cout << '\r' //<< lt::state(s.state) << ' '
                              << (s.download_payload_rate / 1000) << " kB/s "
                              << (s.total_done / 1000) << " kB ("
                              << (s.progress_ppm / 10000) << "%) downloaded ("
                              << s.num_peers << " peers)\x1b[K";
                    std::cout.flush();
                }
                break;
            case lt::torrent_finished_alert::alert_type:
                std::cout << ">> finished : ==" << std::endl;
                goto END;
            case lt::torrent_error_alert::alert_type:
                std::cout << ">> error : ==" << std::endl;
                goto END;
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
END:
    return 0;
}
catch (std::exception &e)
{
    std::cerr << "Error: " << e.what() << std::endl;
}

Magnetlink libtorrent extension plugin, log analysis 他

Magnetlink libtorrent extension plugin, log analysis などなど、
公式のExampleにない使い方の解説もしていますので、

より詳しく知りたい方は、以下もどうぞ
Hello libtorrent (from dev.to)

コメント

最近だと、bittoreent の network の覗いていて、最近は webtorrent を利用する方が多くなったように感じました。

WebTorrent では、TCP、UDPに加えて、WebRTC を利用してデーターのダウンロードとアップロードも行います。
また、Bittorrent では Trackerサーバーとして、TCPとUDPを利用しますが、加えて Websocket を利用した方法もサポートしています。

Nodeで書かれており、機能も分離されており
もしかすると、Bittorrent のネットワークに参加するプログラムを書く場合、WebTorrent を利用する方が、
楽かもしれません..

PS

フルスクラッチでTorrent を書いてみたい方は、
2014年に書いた なぜなにTorrent 2014 で、実際にフルスクラッチでしながらm解説した記事もありますので

こちらもどうぞ..
※ ChromeApp と Dart 1系を利用していて 動きませんが..

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3