B編碼與BT種子檔案分析,以及模仿json-cpp寫一個B編碼決議器
- 1、什么是B編碼
- 2、B編碼格式
- 3、種子檔案結構
- 3.1、主檔案結構
- 3.2、info結構
- 4、簡單的例子了解一下種子檔案和B編碼
- 5、分析JSON-CPP的設計
- 5.1、分析Json::Value::CZString的設計
- 5.2、分析Json::Value的設計
- 5.2.1、類成員設計
- 5.2.2、類方法設計
- 5.3、迭代器的設計
- 5.3.1、Json::ValueIteratorBase
- 5.3.2、Json::ValueConstIterator
- 5.3.3、Json::ValueIterator
- 5.4、總結
- 6、B編碼決議器設計
- 6.1、BEncode::Value
- 6.2、BEncode::ValueIteratorBase
- 6.3、BEncode::ValueConstIterator
- 6.4、BEncode::ValueIterator
- 6.5、決議函式
- 7、總結并附上本文源代碼
1、什么是B編碼
B編碼是種子檔案以及tracker服務器回傳資訊的編碼格式,DHT協議和BT協議傳輸的格式都是經過B編碼壓縮的
2、B編碼格式
B編碼有以下4種資料型別:
- 字串:編碼格式為
<十進制ASCII編碼的長度>:<字串>,需要注意的是字串沒有開始結束符,如4:span,表示字串span, - 整數:編碼格式為
i<十進制ASCII編碼的整數>e,以i開頭,e結尾,中間的數值可以是負數,如i-3e是有效的, - 串列:編碼格式為
l<編碼值>e,以l開頭,e結尾,<編碼值>可以是任意的B編碼的字串、整數、字典或者其他串列,如l4:info3:path,表示由字串info、path組成的串列, - 字典:編碼格式為
d<字串><編碼元素>e,以d開頭,e結尾, 需要注意的是<字串>必須是B編碼的字串,<編碼元素>可以是任意的B編碼的字串、整數、串列或者其他字典,
3、種子檔案結構
種子檔案里的內容其實就是文本,是經過B編碼之后的文本,它主要包含以下欄位
3.1、主檔案結構
- info:必須,一個描述torrent檔案的字典,有兩種可能形式,一種是沒有目錄結構的“單一檔案”,一種是有目錄結構的“多檔案”
- announce:可選,tracker服務器的地址URL(字串)
- announce-list:可選,tracker服務器串列,這是官方規范的一個擴展,向后兼容,用來存盤備用服務器串列
- creation date:可選,torrent檔案的創建時間,為Unix時間戳
- comment:可選,備注資訊
- created by:可選,說明torrent檔案是由哪個程式創建的
- encoding:可選,種子檔案的默認編碼
- nodes:可選,這個欄位包含一系列ip和相應埠的串列,用于連接DHT初始node
3.2、info結構
所有關于下載的檔案的資訊都在這個欄位里,它包括多個子欄位,而且根據下載的是單個檔案還是多個檔案,子欄位會有所不同
單檔案結構如下:
- name:必須,檔案名(字串)
- name.utf-8:可選,內容同上,區別在于使用了UTF-8編碼
- length:檔案長度,單位位元組(整數)
- piece length:必須,每個塊的大小,單位位元組(整數)
- pieces:必須,檔案的特征資訊,該欄位比較大,實際上是種子內包含所有的檔案段的SHA1的校驗值的連接,即將所有檔案按照piece length的位元組大小分成塊,每塊計算一個SHA1值,然后將這些值連接起來就形成了pieces欄位,由于SHA1的校驗值為20Byte,所以該欄位的大小始終為20的整數倍位元組,該欄位是種子檔案中體積最大的部分,可見如果大檔案分塊很小,會造成種子檔案體積十分龐大
- publisher:可選,發布者的名字
- publisher.utf-8:可選,發布者的名字的utf-8編碼
- publisher-url:可選,發布者的網址
- publisher-url.utf-8:可選,發布者網址的utf-8編碼,
多檔案結構如下:
-
files:必須,是一個串列,存盤檔案的名字與大小資訊,該欄位包含以下三個子欄位:
length:必須,檔案的大小(位元組) path:必須,是一個串列,從上到下識別,最末尾的欄位是檔案名,前面的是檔案路徑,內容在下載時不允許更改 path.utf-8:可選,內容同上,區別在于使用了UTF-8編碼 -
name:必須,推薦的檔案夾名,此項可于下載時更改(字串)
-
name.utf-8:可選,內容同上,區別在于使用了UTF-8編碼
-
piece length:必須,每個塊的大小,單位位元組(整數)
-
pieces:必須,檔案的特征資訊,該欄位比較大,實際上是種子內包含所有的檔案段的SHA1的校驗值的連接,即將所有檔案按照piece length的位元組大小分成塊,每塊計算一個SHA1值,然后將這些值連接起來就形成了pieces欄位,由于SHA1的校驗值為20Byte,所以該欄位的大小始終為20的整數倍位元組,該欄位是種子檔案中體積最大的部分,可見如果大檔案分塊很小,會造成種子檔案體積十分龐大
-
publisher:可選,發布者的名字
-
publisher.utf-8:可選,發布者的名字的utf-8編碼
-
publisher-url:可選,發布者的網址
-
publisher-url.utf-8:可選,發布者網址的utf-8編碼,
當種子里包含單個檔案時,name欄位描述的是資源的名稱了,此時name欄位在下載時不允許更改
4、簡單的例子了解一下種子檔案和B編碼
工欲善其事,必先利其器,要想更好地了解B編碼,就需要拿一個種子檔案來分析其中的內容,我們可以自己去隨便一個網站下載一個種子檔案下來,下載的話使用用迅雷之類的軟體就行了,然后使用BEncode Editor這個軟體打開種子,可以看到類似下面的內容,和我們之前描述的差不多

5、分析JSON-CPP的設計
決議器是參考JSON-CPP來寫的,大家可以自行去網上下載JSON-CPP的原始碼,或者 點擊這里 下載bifang框架原始碼,里面src/json/里面就是JSON-CPP的原始碼了
5.1、分析Json::Value::CZString的設計
Json::Value里面有一個CZString類,是為了用于統一管理陣列型別和物件型別的,如下所示
class CZString {
public:
enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy };
CZString(ArrayIndex index);
CZString(char const* str, unsigned length, DuplicationPolicy allocate);
CZString(CZString const& other);
CZString(CZString&& other) noexcept;
~CZString();
CZString& operator=(const CZString& other);
CZString& operator=(CZString&& other) noexcept;
bool operator<(CZString const& other) const;
bool operator==(CZString const& other) const;
ArrayIndex index() const;
// const char* c_str() const; ///< \deprecated
char const* data() const;
unsigned length() const;
bool isStaticString() const;
private:
void swap(CZString& other);
struct StringStorage {
unsigned policy_ : 2;
unsigned length_ : 30; // 1GB max
};
char const* cstr_; // actually, a prefixed string, unless policy is noDup
union {
ArrayIndex index_;
StringStorage storage_;
};
};
從類成員中可以看到,該類可以存盤整型和字串型別的資料,我們設計時可以簡單粗暴地使用std::string來存盤字串型別就行,從后面的代碼中我們可以看到這個類是作為std::map的鍵值來使用的,這樣陣列型別和物件型別都可以用map來表示了,要實作這點,關鍵的一點就是要實作比較運算子的多載,如下所示,可以看到,當CZString表示的是陣列型別時,使用index_去作比較運算,表示的是物件時使用的是cstr_去作比較運算,完美解決問題,
bool Value::CZString::operator<(const CZString& other) const {
if (!cstr_)
return index_ < other.index_;
// return strcmp(cstr_, other.cstr_) < 0;
// Assume both are strings.
unsigned this_len = this->storage_.length_;
unsigned other_len = other.storage_.length_;
unsigned min_len = std::min<unsigned>(this_len, other_len);
JSON_ASSERT(this->cstr_ && other.cstr_);
int comp = memcmp(this->cstr_, other.cstr_, min_len);
if (comp < 0)
return true;
if (comp > 0)
return false;
return (this_len < other_len);
}
bool Value::CZString::operator==(const CZString& other) const {
if (!cstr_)
return index_ == other.index_;
// return strcmp(cstr_, other.cstr_) == 0;
// Assume both are strings.
unsigned this_len = this->storage_.length_;
unsigned other_len = other.storage_.length_;
if (this_len != other_len)
return false;
JSON_ASSERT(this->cstr_ && other.cstr_);
int comp = memcmp(this->cstr_, other.cstr_, this_len);
return comp == 0;
}
5.2、分析Json::Value的設計
5.2.1、類成員設計
可以看到Json::Value是使用了一個聯合體來作為類成員使用的,由于每個Json::Value只可能代表一種型別(總不能又代表陣列型別又代表物件型別吧,,,),所以使用聯合體可以最大程度地節省空間,比較巧妙的做法,我們平時也可以在特定的場合使用這種技巧
using LargestInt = int;
using LargestUInt = unsigned int;
typedef std::map<CZString, Value> ObjectValues;
union ValueHolder {
LargestInt int_;
LargestUInt uint_;
double real_;
bool bool_;
char* string_; // if allocated_, ptr to { unsigned, char[] }.
ObjectValues* map_;
} value_;
5.2.2、類方法設計
這里看幾個典型的方法設計就行,大部分比較簡單大家可以自己去看看
- append方法的實作如下,可以看到由于append是給陣列型別的資料使用的,所以每次插入資料之后不需要讓map重新排序啥的,可以直接在指定位置構造元素即可,所以使用了std::map的emplace方法(size方法的回傳值是ArrayIndex,如果不理解ArrayIndex為什么可以替代CZString的得自行去補一下C++的基礎,,,)
Value& Value::append(const Value& value) { return append(Value(value)); }
Value& Value::append(Value&& value) {
JSON_ASSERT_MESSAGE(type() == nullValue || type() == arrayValue,
"in Json::Value::append: requires arrayValue");
if (type() == nullValue) {
*this = Value(arrayValue);
}
return this->value_.map_->emplace(size(), std::move(value)).first->second;
}
- find方法的實作如下,沒什么特別的,就是使用了std::map的find而已,多載的operator[](const char* key)之類的方法中也是使用了這個find來實作的,難度不大,大家自己看一遍就明白了
Value const* Value::find(char const* begin, char const* end) const {
JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue,
"in Json::Value::find(begin, end): requires "
"objectValue or nullValue");
if (type() == nullValue)
return nullptr;
CZString actualKey(begin, static_cast<unsigned>(end - begin),
CZString::noDuplication);
ObjectValues::const_iterator it = value_.map_->find(actualKey);
if (it == value_.map_->end())
return nullptr;
return &(*it).second;
}
const Value& Value::operator[](const char* key) const {
Value const* found = find(key, key + strlen(key));
if (!found)
return nullSingleton();
return *found;
}
5.3、迭代器的設計
5.3.1、Json::ValueIteratorBase
迭代器基類設計如下,可以看到類成員的定義為 Value::ObjectValues::iterator current_,這只是簡單托管了Json::Value中**map_**的迭代器而已,沒有什么特殊的地方,
class JSON_API ValueIteratorBase {
public:
using iterator_category = std::bidirectional_iterator_tag;
using size_t = unsigned int;
using difference_type = int;
using SelfType = ValueIteratorBase;
bool operator==(const SelfType& other) const { return isEqual(other); }
bool operator!=(const SelfType& other) const { return !isEqual(other); }
difference_type operator-(const SelfType& other) const {
return other.computeDistance(*this);
}
/// Return either the index or the member name of the referenced value as a
/// Value.
Value key() const;
/// Return the index of the referenced Value, or -1 if it is not an
/// arrayValue.
UInt index() const;
/// Return the member name of the referenced Value, or "" if it is not an
/// objectValue.
/// \note Avoid `c_str()` on result, as embedded zeroes are possible.
String name() const;
/// Return the member name of the referenced Value. "" if it is not an
/// objectValue.
/// \deprecated This cannot be used for UTF-8 strings, since there can be
/// embedded nulls.
JSONCPP_DEPRECATED("Use `key = name();` instead.")
char const* memberName() const;
/// Return the member name of the referenced Value, or NULL if it is not an
/// objectValue.
/// \note Better version than memberName(). Allows embedded nulls.
char const* memberName(char const** end) const;
protected:
/*! Internal utility functions to assist with implementing
* other iterator functions. The const and non-const versions
* of the "deref" protected methods expose the protected
* current_ member variable in a way that can often be
* optimized away by the compiler.
*/
const Value& deref() const;
Value& deref();
void increment();
void decrement();
difference_type computeDistance(const SelfType& other) const;
bool isEqual(const SelfType& other) const;
void copy(const SelfType& other);
private:
Value::ObjectValues::iterator current_;
// Indicates that iterator is for a null value.
bool isNull_{true};
public:
// For some reason, BORLAND needs these at the end, rather
// than earlier. No idea why.
ValueIteratorBase();
explicit ValueIteratorBase(const Value::ObjectValues::iterator& current);
};
設計上是沒有什么特別的,因為是利用std::map的迭代器進行封裝的,下面簡單看一下幾個方法的實作就行,我們封裝B編碼的也采用差不多的方法
Value& ValueIteratorBase::deref() { return current_->second; }
const Value& ValueIteratorBase::deref() const { return current_->second; }
void ValueIteratorBase::increment() { ++current_; }
void ValueIteratorBase::decrement() { --current_; }
5.3.2、Json::ValueConstIterator
繼承于迭代器基類,非常簡單,大家自行看一下就行
class JSON_API ValueIterator : public ValueIteratorBase {
friend class Value;
public:
using value_type = Value;
using size_t = unsigned int;
using difference_type = int;
using reference = Value&;
using pointer = Value*;
using SelfType = ValueIterator;
ValueIterator();
explicit ValueIterator(const ValueConstIterator& other);
ValueIterator(const ValueIterator& other);
private:
/*! \internal Use by Value to create an iterator.
*/
explicit ValueIterator(const Value::ObjectValues::iterator& current);
public:
SelfType& operator=(const SelfType& other);
SelfType operator++(int) {
SelfType temp(*this);
++*this;
return temp;
}
SelfType operator--(int) {
SelfType temp(*this);
--*this;
return temp;
}
SelfType& operator--() {
decrement();
return *this;
}
SelfType& operator++() {
increment();
return *this;
}
/*! The return value of non-const iterators can be
* changed, so the these functions are not const
* because the returned references/pointers can be used
* to change state of the base class.
*/
reference operator*() { return deref(); }
pointer operator->() { return &deref(); }
};
5.3.3、Json::ValueIterator
繼承于迭代器基類,非常簡單,大家自行看一下就行
class JSON_API ValueIterator : public ValueIteratorBase {
friend class Value;
public:
using value_type = Value;
using size_t = unsigned int;
using difference_type = int;
using reference = Value&;
using pointer = Value*;
using SelfType = ValueIterator;
ValueIterator();
explicit ValueIterator(const ValueConstIterator& other);
ValueIterator(const ValueIterator& other);
private:
/*! \internal Use by Value to create an iterator.
*/
explicit ValueIterator(const Value::ObjectValues::iterator& current);
public:
SelfType& operator=(const SelfType& other);
SelfType operator++(int) {
SelfType temp(*this);
++*this;
return temp;
}
SelfType operator--(int) {
SelfType temp(*this);
--*this;
return temp;
}
SelfType& operator--() {
decrement();
return *this;
}
SelfType& operator++() {
increment();
return *this;
}
/*! The return value of non-const iterators can be
* changed, so the these functions are not const
* because the returned references/pointers can be used
* to change state of the base class.
*/
reference operator*() { return deref(); }
pointer operator->() { return &deref(); }
};
5.4、總結
JSON-CPP的實作還是非常巧妙的,里面的很多設計和stl很相似,類提供的包括迭代器的設計也是這樣的,這也是JSON-CPP使用起來很簡單的一個原因.我們可以很輕易地借鑒這些優秀的代碼來設計出一個B編碼的決議器
6、B編碼決議器設計
6.1、BEncode::Value
和JSON-CPP的實作差不多,舍棄了一些沒用的功能,簡化了代碼,如下所示
class Value
{
friend class ValueIteratorBase;
private:
class CZString
{
public:
CZString(size_t index);
CZString(const std::string& str);
CZString(const CZString& other);
~CZString();
CZString& operator=(const CZString& other);
bool operator<(const CZString& other) const;
bool operator==(const CZString& other) const;
std::string* data() const { return m_str; }
size_t index() const { return m_index; }
private:
std::string* m_str;
size_t m_index;
};
public:
typedef std::map<CZString, Value> DictionaryValues;
using const_iterator = ValueConstIterator;
using iterator = ValueIterator;
enum Type
{
BCODE_INTEGER = 0,
BCODE_STRING,
BCODE_LIST,
BCODE_DICTIONARY,
};
Value(Type type = BCODE_INTEGER);
Value(int64_t value);
Value(const std::string& value);
Value(const Value& other);
~Value();
Type getType() const { return m_type; }
bool isInteger() const { return m_type == BCODE_INTEGER; }
bool isString() const { return m_type == BCODE_STRING; }
bool isList() const { return m_type == BCODE_LIST; }
bool isDictionary() const { return m_type == BCODE_DICTIONARY; }
size_t size() const;
bool empty() const;
std::string typeToString() const;
void setType(Type type);
void clear();
void resize(size_t newSize);
/**
* brief: You may need to say 'value[0u]' to get your compiler to distinguish
* this from the operator[] which takes a string
*/
Value& operator[](size_t index);
Value& operator[](const std::string& key);
void swap(Value& other);
Value& operator=(const Value& other);
bool operator<(const Value& other) const;
bool operator>(const Value& other) const;
bool operator<=(const Value& other) const;
bool operator>=(const Value& other) const;
bool operator==(const Value& other) const;
bool operator!=(const Value& other) const;
int64_t asInt() const;
std::string asString() const;
Value& append(const Value& value);
bool insert(size_t index, const Value& newValue);
Value get(size_t index, const Value& defaultValue) const;
Value get(const std::string& key, const Value& defaultValue) const;
const_iterator find(const std::string& key) const;
iterator find(const std::string& key);
bool isMember(const std::string& key) const;
void removeMember(const std::string& key);
bool removeMember(const std::string& key, Value* removed);
bool removeIndex(size_t index, Value* removed);
std::vector<std::string> getMemberNames() const;
const_iterator begin() const;
const_iterator end() const;
iterator begin();
iterator end();
private:
void free();
private:
Type m_type;
union ValueHolder
{
int64_t m_int;
std::string* m_string;
DictionaryValues* m_map;
} m_value;
};
6.2、BEncode::ValueIteratorBase
class ValueIteratorBase
{
public:
using SelfType = ValueIteratorBase;
ValueIteratorBase();
explicit ValueIteratorBase(const Value::DictionaryValues::iterator& current);
public:
bool operator==(const SelfType& other) const { return isEqual(other); }
bool operator!=(const SelfType& other) const { return !isEqual(other); }
int64_t operator-(const SelfType& other) const { return other.computeDistance(*this); }
Value key() const;
size_t index() const;
std::string name() const;
protected:
Value& deref() { return m_current->second; }
const Value& deref() const { return m_current->second; }
void increment() { m_current++; }
void decrement() { m_current--; }
int64_t computeDistance(const SelfType& other) const;
bool isEqual(const SelfType& other) const;
void copy(const SelfType& other);
private:
Value::DictionaryValues::iterator m_current;
bool m_isNull = true;
};
6.3、BEncode::ValueConstIterator
class ValueConstIterator : public ValueIteratorBase
{
friend class Value;
public:
using value_type = const Value;
using reference = const Value&;
using pointer = const Value*;
using SelfType = ValueConstIterator;
ValueConstIterator();
ValueConstIterator(const ValueIterator & other);
private:
explicit ValueConstIterator(const Value::DictionaryValues::iterator& current);
public:
SelfType& operator=(const ValueIteratorBase& other)
{
copy(other);
return *this;
}
SelfType operator++(int)
{
SelfType temp(*this);
++*this;
return temp;
}
SelfType operator--(int)
{
SelfType temp(*this);
--*this;
return temp;
}
SelfType& operator--()
{
decrement();
return *this;
}
SelfType& operator++()
{
increment();
return *this;
}
reference operator*() const { return deref(); }
pointer operator->() const { return &deref(); }
};
6.4、BEncode::ValueIterator
class ValueIterator : public ValueIteratorBase
{
friend class Value;
public:
using value_type = Value;
using reference = Value&;
using pointer = Value*;
using SelfType = ValueIterator;
ValueIterator();
ValueIterator(const SelfType& other);
explicit ValueIterator(const ValueConstIterator& other);
private:
explicit ValueIterator(const Value::DictionaryValues::iterator& current);
public:
SelfType& operator=(const SelfType& other)
{
copy(other);
return *this;
}
SelfType operator++(int)
{
SelfType temp(*this);
++*this;
return temp;
}
SelfType operator--(int)
{
SelfType temp(*this);
--*this;
return temp;
}
SelfType& operator--()
{
decrement();
return *this;
}
SelfType& operator++()
{
increment();
return *this;
}
reference operator*() { return deref(); }
pointer operator->() { return &deref(); }
};
6.5、決議函式
決議函式是自己寫的,遞回決議B編碼,沒有什么特別的地方,大家可以自行去下載源代碼去除錯一下,由于是個人撰寫的,沒有經過大量的測驗,大家如果測驗了有問題歡迎提出改進意見
7、總結并附上本文源代碼
本文先是介紹了B編碼的格式及用途,再介紹了BT種子檔案的格式,最后通過模仿JSON-CPP的代碼自己寫了一個B編碼的決議器,
這里附上源代碼一份,有錯漏的歡迎大家提出修改意見,謝謝
c++撰寫的B編碼決議器原始碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/287420.html
標籤:其他
上一篇:TCP通信細節—貳
