libTorrent簡單使用

ltsession(會話)是最核心的,種子只有加入進去方可下載上傳等動作。

session_handle: 主要負責對session的操作。

torrent_handle: 主要負責對種子的操作以及狀態查詢。

session.pop_alerts(): 可以獲取從上次調用以來的所有新警報的列表。每種特定類型的警報可能包括特定於消息類型的附加狀態。所有警報都實現一個message()函數,該函數輸出警報消息的相關信息。這可以方便地記錄事件。

下面先針對各小功能進行代碼實現,最好再整合一個完整的代碼例子

1. 添加種子/磁力(並下載)

<code>lt::session ses; \t\t\t\t\t\t\t\t\t//定義session對象
lt::add_torrent_params p = lt::parse_magnet_uri("magnet:?xt=urn:btih:......"); // 解析磁力鏈接
p.save_path = "."; // 設置保存到當前目錄
lt::torrent_handle h = ses.add_torrent(p); //添加到session,並獲得其句柄(該句阻塞執行,有結果才返回)/<code>
<code>lt::session ses; \t\t\t\t\t\t\t\t\t//定義session對象
auto ti = std::make_shared<:torrent_info>(torrentFilePath); //通過種子文件 定義 torrent_info對象
lt::add_torrent_params p;
p.ti = ti;\t\t\t\t\t\t\t\t\t\t//這兒很重要, 必須要中torrent_info對象傳遞進來
p.save_path = ".";
p.userdata = static_cast<void>(new std::string(torrentFilePath)); //這根據自己需要而定具體數據
ses.async_add_torrent(std::move(p)); //這兒是異步調用, 調完就離開,獲取不到 session_handle/<void>/<code>

以上為 通過磁力鏈接 和 torrent 種子這兩種方式添加到session的簡單例子。torrent_info類又諸多構造函數,對象定義方法也因此非常多,根據需要選擇合適的。

另外添加種子到session的方法有 同步調用(阻塞耗時)和異步調用(立即返回)兩種,也根據需要選擇。

2. 警報

直接上代碼講解

<code>for (;;) {
std::vector<:alert> alerts;
ses.pop_alerts(&alerts); //獲取自上次調用以來的新警報列表

for (lt::alert const* a : alerts) { //遍歷處理自己需要的警報。
std::cout << a->message() << std::endl; \t\t//打印警報信息

// 下面是針對各種類型的警報進行處理例子
if (lt::alert_cast<:torrent_finished_alert>(a)) {
goto done;
}
if (lt::alert_cast<:torrent_error_alert>(a)) {
\tgoto done;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
done:
std::cout << "done, shutting down" << std::endl;/<code>

我們可以用‘警報掩碼’抓取我們關心的警報(有很多種類),如下 :

<code>lt::settings_pack pack;
pack.set_int(lt::settings_pack::alert_mask // 設置掩碼 關心 下面三種掩碼
, lt::alert::error_notification
| lt::alert::storage_notification
| lt::alert::status_notification);
lt::session ses(pack);/<code>
「P2P」 - libTorrent簡單使用

警報掩碼 之 掩碼類別

「P2P」 - libTorrent簡單使用

警報類型

3. 更新配置選項

session啟動後,可以通過調用 apply_settings() 更新配置,

<code>lt::settings_pack pack;
pack.set_int(lt::settings_pack::alert_mask,lt::alert::status_notification);
ses.apply_settings(pack); // 更新配置 alert_mask/<code>

雖然這樣可以更新配置,但有些設置最好在啟動會話之前設置,比如 listen_interfaces,以避免出現競爭條件。如果使用默認設置啟動會話,然後立即更改它們,則仍會有一個應用默認設置的窗口。

更改設置可能會觸發偵聽套接字關閉和重新打開,併發送NAT-PMP、UPnP更新。因此,將設置更新批處理到單個調用中通常是一個好主意。

4. 獲取種子狀態

<code>for(;;){
ses. post_torrent_updates(); // 發送 alert::status_notification
\t\t\t
\t\tstd::this_thread::sleep_for(250);

std::vector<:alert> alerts;
ses.pop_alerts(&alerts); //獲取自上次調用以來的新警報列表

for (lt::alert const* a : alerts) {
// 處理 state_update_alert
if (state_update_alert* p = alert_cast<state>(a)) {
\t\t\t// 包含自上次調用以來更新的種子狀態

std::vector<torrent> status = p->stauts;
\t\t\t......
}
}
....
}/<torrent>/<state>/<code>

5. 恢復種子

恢復下載時,BT引擎必須恢復正在下載種子的狀態,特別是文件的哪些部分已下載, 有兩種方法可以做到這一點:

(1) 從磁盤讀取已下載文件片段,並將其與預定的哈希值進行比較。

(2) 保存已下載的片段(和部分片段)的狀態到磁盤,並在恢復時重新加載。

如果添加種子時沒有提供恢復數據,那麼libtorrent將默認使用上面第1點。


libtorrent有提供函數,實現保存恢復數據的功能:

<code> torrent_handle.save_resume_data(torrent_handle::save_info_dict) ; /<code>

調用該函數的時機:

(1). 人為地選中某個種子,操作‘保存恢復數據’。

(2). 每個種子加載完成時。

(3). 關閉session之前。

注意:調用該函數並沒將恢復數據保存到磁盤上面, 調用該函數後實際上會發出警報:

save_resume_data_alert(若成功) 或 save_resume_data_failed_alert(若失敗)

<code>\telse if (save_resume_data_alert* p = alert_cast<save>(a))
\t{
\t\ttorrent_handle h = p->handle; // 獲取torrent_handle,目的是獲取torrent_status
\t\tauto const buf = write_resume_data_buf(p->params); // p->params為 add_torrent_params 類型
\t\ttorrent_status st = h.status(torrent_handle::query_save_path);
\t\tsave_file(resume_file(st.info_hash), buf); //保存恢復數據到名為info_hash(長度40)的文件中
\t}
\telse if (save_resume_data_failed_alert* p = alert_cast<save>(a))
\t{
\t\t// 如果不需要保存恢復數據,可以不打印錯誤信息
\t\treturn p->error == lt::errors::resume_data_not_modified;
\t}/<save>/<save>/<code>


6. session對象的析構銷燬

默認情況下會話析構函數會被阻塞。關閉時,需要聯繫追蹤器以停止種子,其他未完成的操作需要取消。關閉有時可能需要幾秒鐘,主要是因為跟蹤器沒有響應(和超時)以及DNS服務器沒有響應。DNS查找在失控時尤其難以中止。

為了能夠異步地開始銷燬等待,可以調用session::abort()。它返回一個session_proxy對象,它是一個句柄,用於在銷燬會話狀態時保持會話狀態。它故意不提供任何會話操作,因為它正在關閉。

擁有session_proxy對象後,會話析構函數不會阻塞。但是session_proxy析構函數卻將被阻塞。

這可用於並行關閉多個會話或應用程序的其他部分。


7. 完整實例

<code>#include <iostream>
#include <thread>
#include <chrono>
#include <fstream>

#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>
#include <libtorrent>

using clk = std::chrono::steady_clock;

// 返回 種子各種狀態的名稱
char const* state(lt::torrent_status::state_t s)
{
switch(s) {
case lt::torrent_status::checking_files: return "checking";
case lt::torrent_status::downloading_metadata: return "dl metadata";
case lt::torrent_status::downloading: return "downloading";
case lt::torrent_status::finished: return "finished";

case lt::torrent_status::seeding: return "seeding";
case lt::torrent_status::allocating: return "allocating";
case lt::torrent_status::checking_resume_data: return "checking resume";
default: return "<>";
}
}

int main(int argc, char const* argv[]) try
{
\t\tif (argc != 2) {
\tstd::cerr << "usage: " << argv[0] << " <magnet-url>" << std::endl;
\t\treturn 1;
\t\t}

lt::settings_pack pack;
pack.set_int(lt::settings_pack::alert_mask
, lt::alert::error_notification
| lt::alert::storage_notification
| lt::alert::status_notification);

lt::session ses(pack);
clk::time_point last_save_resume = clk::now();

// 從磁盤加載恢復數據,並在添加磁力鏈接時傳遞它
std::ifstream ifs(".resume_file", std::ios_base::binary);
ifs.unsetf(std::ios_base::skipws);
std::vector<char> buf{std::istream_iterator<char>(ifs) , std::istream_iterator<char>()};

lt::add_torrent_params p = lt::read_resume_data(buf);
lt::add_torrent_params magnet = lt::parse_magnet_uri(argv[1]);
if (p.info_hash != magnet.info_hash) { //判斷恢復數據和磁力鏈接是否匹配,以磁力鏈接為準
p = std::move(magnet);
}
p.save_path = "."; // 保存到當前目錄
ses.async_add_torrent(std::move(p));

lt::torrent_handle h; //當我們收到add_torrent_alert時,我們設置它
for (;;) {
std::vector<:alert> alerts;
ses.pop_alerts(&alerts);

for (lt::alert const* a : alerts) {
if (auto at = lt::alert_cast
<:add_torrent_alert>(a)) {
h = at->handle;
}
// 如果收到 torrent_finished_alert或torrent_error_alert,則退出程序
if (lt::alert_cast<:torrent_finished_alert>(a)) {
h.save_resume_data();
goto done;
}
if (lt::alert_cast<:torrent_error_alert>(a)) {
std::cout << a->message() << std::endl;
goto done;
}

// resume data準備就緒,保存到磁盤文件
if (auto rd = lt::alert_cast<:save_resume_data_alert>(a)) {
std::ofstream of(".resume_file", std::ios_base::binary);
of.unsetf(std::ios_base::skipws);
auto const b = write_resume_data_buf(rd->params);
of.write(b.data(), b.size());
}

if (auto st = lt::alert_cast<:state_update_alert>(a)) {
if (st->status.empty()) continue;

// 因為我們只有一個種子,所以我們知道 這個status是哪個種子的
lt::torrent_status const& s = st->status[0];
std::cout << "\\r" << state(s.state) << " "
<< (s.download_payload_rate / 1000) << " kB/s "
<< (s.total_done / 1000) << " kB ("
<< (s.progress_ppm / 10000) << "%) downloaded\\\\x1b[K";
std::cout.flush();
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));

// post state_update_alert 更新種子輸出狀態
ses.post_torrent_updates();

// 每30秒保存恢復數據一次
if (clk::now() - last_save_resume > std::chrono::seconds(30)) {

\t\th.save_resume_data();
\t\tlast_save_resume = clk::now();
}
}

// 理想情況下,我們應該在這裡保存恢復數據
\tdone:
\t\tstd::cout << "\\ndone, shutting down" << std::endl;
}
catch (std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}/<char>/<char>/<char>/<magnet-url>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<libtorrent>/<fstream>/<chrono>/<thread>/<iostream>/<code>


分享到:


相關文章: