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 という感じです。
//
// 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;
}
//
// 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"にまとめます。
#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
//
// 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 の ネットワークではデータのダウンロードをする際に、同時にデーターのアップロードします。
#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系を利用していて 動きませんが..