第一章的目的是大致瞭解libed2k怎麼使用,libed2k庫自帶了一個測試工程conn,這一章我們將分析conn,讓從我們最關心的下載文件開始。
1.1 解析ed2k鏈接
通常在網上分享的電驢資源時都是通過ed2k鏈接的方式,ED2K鏈接格式爲:
ed2k://|file|cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso|3420557312|B58548681854236C7939003B583A8078|h=TQXCJQ2QAYCXKMFQKKHRZJ4WE34T3TDA|/
解析鏈接的代碼在libed2k\include\libed2k\file.hpp中:
static emule_collection_entry fromLink(const std::string& strLink);
它的實現如下:
(1)讀取0-13字節並檢查是否爲“ed2k://|file|”
(2)讀取從13字節開始到下一個“|”,得到文件名。
(3)讀取從文件名之後到下一個“|”的位置,得到文件大小
(4)讀取從文件大小之後到下一個“|”的位置,得到hash值。
(5)丟棄剩餘部分“h=TQXCJQ2QAYCXKMFQKKHRZJ4WE34T3TDA”
得到文件名,大小和哈希值後存放到結構體emule_collection_entry中。這個結構體內的成員與這些值一一對應。直接用if(emule_collection_entry)函數可以判斷是否是有意義的一個結構體(重載了bool ()操作符)。
1.2 emule任務清單
emule文件集合類(叫做emule任務清單或許更恰當)emule_collection封裝了std::deque<emule_collection_entry>並提供了一些額外的操作。
靜態函數:
(1)提供從“ed2k://”鏈接到emule_collection_entry的轉換的fromLink方法(見1.1)。
(2)提供從文件名,文件大小和哈希值生成“ed2k://”鏈接的toLink方法。
(3)從磁盤文件中讀取任務清單的fromFile方法。
成員函數:
(1)保存任務清單到文件的save方法。
(2)添加文件到任務清單的add_file方法(需要文件名,文件大小和哈希值)。
(3)添加“ed2k://”鏈接到任務清單的add_link方法。
(4)從任務清單中根據序號(index)獲取emule_collection_entry元素並生成“ed2k://”鏈接的get_ed2k_link方法。
(5)重載的==操作符,用於對比兩個emule_collection是否相同
成員變量:
(1)emule_collection_entry集合
(2)集合名字(string name;),這個值保留未用
1.3 添加鏈接並下載
參照qtlibed2k\qed2ksession.cpp的QPair<Transfer,ErrorCode> QED2KSession::addLink(QString strLink, bool resumed /* = false */),在conn工程中添加一個命令download_addlink。
case cc_download_addlink:
{
DBG("Add link and download: " << strArg);
libed2k::emule_collection_entry ece = libed2k::emule_collection::fromLink(strArg);
if (!ece)
{
DBG("Incorrect link");
}
else
{
DBG("Link is corrent, add transfer.");
libed2k::add_transfer_params atp;
atp.file_hash = ece.m_filehash;
boost::filesystem::path p(strIncomingDirectory);
p.append(ece.m_filename);
atp.file_path = p.string();
atp.file_size = ece.m_filesize;
atp.duplicate_is_error = true;
atp.dump();
libed2k::transfer_handle h = ses.add_transfer(atp); //We usually need to save the transfer handler to get information or abort the transfer mission.
//As a simple example we just ignore it here.
}
break;
}
測試:conn 176.103.48.36 4184 D:\123,然後在連接上服務器後使用新增的download_addlink命令:
download_addlink:ed2k://|file|xxxx.mp4|574013397|039FDC9AD3CDC79E952540BD3ACE5982|/
結果如下圖:
可以看到大部分的顯示內容不是我們要的,我關心的是連接到誰,從它們那裏下載和上傳到它們的速度怎麼樣。下載進度信息等。
1.4 libed2k的日誌系統
日誌系統的初始化是通過LOGGER_INIT(x)宏控制,這個宏參數的意義是日誌功能,定義如下:
const unsigned char LOG_CONSOLE = 1;
const unsigned char LOG_FILE = 2;
const unsigned char LOG_ALL = '\xFF';
這三個選項的意思按其字面意思分別是將日誌輸出到控制檯、文件或者同時寫入這兩者。
libed2k使用的是boost::logger(libed2k\src\log.cpp),在這裏我們需要將上文中的dbg全部屏蔽可以設置過濾器的級別爲info:
#include <boost/logging/format.hpp>
//...
void init_logs(unsigned char log_destination /*= LC_ALL*/)
{
//...
g_l_filter()->set_enabled(level::info);
}
可選的level包括:
enum {
disable_all = (type)-1,
enable_all = 0,
debug = 1000,
info = 2000,
warning = 3000,
error = 4000,
fatal = 5000
};
具體BOOST日誌庫的使用方法可以閱讀boost庫的官方文檔。
重試1.3添加ed2k鏈接的步驟,現在的輸出是這樣:
可以見到原來輸出的一大堆dbg信息已經不見了。但是由於輸出日誌大部分是level::debug這個級別,消息過濾導致我們想看的信息也一併消失了,爲保證conn里正常的信息輸出我們可以將conn.cpp裏的DBG全部替換成APP宏(APP對應info等級)。
接下來我們將研究alert事件通知,然後將我們關心的通知信息從level::debug改爲level::info級別。
1.5 消息過濾
書接上文,在往libed2k的任務隊列中插入一個“ed2k://”鏈接後,程序開始了從搜索peer到下載文件的過程。在這過程中輸出了海量的信息,而且大部分都不是我們關心的。現在我希望能過濾它,只獲取速度和下載源等幾項信息。
libed2k採用了和libtorrent相同的異步消息通知方式,程序在完成一項操作後發送響應的alert類型對象到alert隊列(alert_manager::post_alert_should/post_alert)。libed2k的使用者可以使用libed2k::session的set_alert_mask方法設定需要接收的alert類型。在conn項目中set_alert_mask的參數被設置爲alert::all_categories(接收所有類型的alert),這也是爲什麼上例中會有輸出大量信息的原因。
在conn項目中,接收和處理(這裏僅打印輸出)alert的函數是通過一個boost定時器完成,該定時器被設置爲三秒後獲取一次alert列表,代碼如下:
boost::asio::deadline_timer alerts_timer(io, boost::posix_time::seconds(3));
//...
alerts_timer.async_wait(
boost::bind(alerts_reader, boost::asio::placeholders::error, &alerts_timer, &ses));
alerts_timer三秒後調用一次alerts_reader(error_code, &alerts_timer, &ses),其中的error_code參數由boost::asio::deadline_timer負責傳遞,有關error_code的意義,見以下鏈接https://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/reference/basic_deadline_timer/async_wait.html。另外需要注意的是在沒有發生錯誤時alerts_reader內部會再次調用alerts_timer.async_wait以達到重複觸發定時器每隔三秒執行一次alerts_reader的目的。
1.6 conn程序框架分析
在繼續深入學習libed2k之前,需要對conn這個測試程序的運行機制做一些瞭解以便在需要的時候修改一些代碼驗證我們的想法。
conn程序非常小源碼只有不到一千行,非常小巧但是具有大部分我們下載文件所需要的功能。
由以下幾個函數組成:
- int main(int argc, char* argv[])
- CONN_CMD extract_cmd(const std::string& strCMD, std::string& strArg)
- void alerts_reader(const boost::system::error_code& ec, boost::asio::deadline_timer* pt, libed2k::session* ps)
- void save_fast_resume(const boost::system::error_code& ec, boost::asio::deadline_timer* pt, libed2k::session* ps)
其中在main函數中主要做了以下幾件事:
- 初始化日誌模塊
- 從輸入參數argv中依次解析“服務器地址”,“服務器端口”和“本地硬盤文件目錄”
- 創建、修改會話配置
- 以配置爲參數創建會話
- 創建從session獲取alert事件的定時器,並將alerts_reader設爲定時器回調。
- 創建保存斷點續傳信息的定時器,並將save_fast_resume設置爲定時器回調。
- 用第二步解析的服務器IP和端口連接met服務器。
- 開啓一個輸入循環,接收用戶輸入的命令(以冒號分隔的命令和命令參數對)並翻譯成CONN_CMD命令,然後按照命令執行對應的代碼。
- 如用戶輸入了quit命令則關閉alert事件定時器和save_fast_resume定時器。
- 等待io service線程退出。
支持的用戶命令包括以下幾類:
- 在服務器上搜索資源,包括“search”,“simplesearch”和“simplesearch”
- 下載搜索到的資源,包括“load”,“loadall”
- 在下載列表中移除一個任務,"remove"
- 打印下載列表所有任務,"dump"
- 連接和斷開(程序輸入參數指定的)服務器"connect",“disconnect”
- 嘗試監聽本地的p2p端口,“listen”
- 打印各下載任務的上傳和下載速度,"tr"
- 添加分享目錄“share”和取消分享目錄“unshare”(取消分享並未實際實現)
- 添加分享文件和取消分享文件,“sharef”和“unsharef”(都沒有實際實現)
- 保存和載入下載列表中所有任務的斷點續傳信息(在當前運行目錄下),“save”和“restore”
- 在第一章中新增的從“ed2k://”鏈接增加下載任務的“download_addlink”
(注:在調試save時會偶發weak_ptr引起的異常,這裏未深究其原因。)