DHT协议是BT协议中的一部分,也是一个辅助性的协议。HTTP协议中用
来定位资源也就是html文本是用URL这样的协议的,而在BT或者说P2P的
世界中,没有了以前那样可以直接定位的服务器,所以需要能够动态的掌
握到资源的分布,那DHT协议就是BT中用来定位资源的协议,具体的不多
说,可以看看官方网站对于BT或者DHT十分详尽的描述:
http://www.bittorrent.org/beps//bep_0003.html
或者去看看其他人翻译出来的文章理解理解:
http://blog.****.net/xxxxxx91116/article/details/7970815
总之,实现DHT协议是BT的前提,是为了能够找到infohash这样一个种子用
hash算法产生的20个字符的字符串,也是找到种子的前提。
我们知道任何应该架构于网络的应用程序,只要是使用的TCP/IP协议的,
必然是基于第三层的IP协议,和第四层的TCP或者UDP协议。当然,我们所说
的驱动级网络编程不在此列。那对于使用UDP传输数据的DHT协议来说,必然
需要对传输或者说交流的数据进行编码,这个编码就是B编码,我找到一个C#
写出这个编码的代码:
http://www.cnblogs.com/technology/p/BEncoding.html,稍加改动成
C++的B编码程序,可以跑起来,但是有一个致命的问题存在,容后再说,先贴
代码:
#define USING
#include "DataStruction.h"
#include <string>
using namespace std; /*
https://github.com/CreateChen/Bencode
transfer the CreateChen's c# code into c++, the thought is very impressive!
*/ enum EncodeState
{
KEY,
VALUE,
}; class BCode
{
private:
static int index;
static BaseData* RealDecodeToDic(string str, int& index, EncodeState state); public:
static BaseData* DecodeToDic(string str); static string EncodeToStr(BaseData*);
};
#include "BCode.h"
#include <sstream> int BCode::index = ; //refactoring! to support the users a simple interface, to delegate the real working function inner the interface!
BaseData* BCode::DecodeToDic(string str)
{
return RealDecodeToDic(str, BCode::index, VALUE);
} //the recursion is for analyzing the dictionary!
//it must can be used in other place!!
BaseData* BCode::RealDecodeToDic(string str, int& index, EncodeState state)
{
DicData* dicData = new DicData();
char c = str[index]; while( c != 'e')
{
if(c == 'd')
{
index ++;
return RealDecodeToDic(str, index, KEY);
}
if(c == 'i')
{
string returnStr = "";
index ++;
//c = str[index];
while(str[index] != 'e')
{
returnStr += str[index];
index++;
//c = str[index];
}
//transfer string to char, then transfer char to int
IntData * intData = new IntData();
//int returnInt = atoi(returnStr.c_str());
intData->SetValue(atoi(returnStr.c_str()));
return intData;
}
if(c == 'l')
{
index++;
ListData* listData = new ListData();
while (str[index] != 'e')
{
listData->add(RealDecodeToDic(str, index, VALUE));
index++;
}
return listData;
}
if('' < c && c <= '')
{
string returnString = "";
string contentString = "";
while(str[index] != ':')
{
returnString += str[index];
index++;
}
int stringLength = atoi(returnString.c_str());
for (int i = ; i < stringLength; i++)
{
contentString += str[index + ];
index++;
} if(state == VALUE)
{
StrData* strData = new StrData();
strData->SetValue(contentString);
return strData;
}
index++;
dicData->add(contentString, RealDecodeToDic(str, index, VALUE));
state = KEY;
index++;
}
c = str[index];
}
return dicData;
} //a kind of recursion! it's smart!
string BCode::EncodeToStr(BaseData* baseData)
{
string newString;
if(baseData->GetDataType() == B_DIC)
{
DicData* dicData = static_cast<DicData*>(baseData);
map<string, BaseData*> newMap = dicData->GetValue();
newString.append("d"); for(map<string, BaseData*>::iterator it = newMap.begin(); it != newMap.end(); it++)
{
stringstream ss;
ss << (it->first).length();
newString = newString.append(ss.str() )+ ":" + it->first; BaseData* recursionBaseData = it->second;
newString.append(EncodeToStr(recursionBaseData));
}
newString.append("e");
} if(baseData->GetDataType() == B_INT)
{
IntData* intData = static_cast<IntData*>(baseData);
//int iValue = intData->GetValue();
stringstream ss;
ss << (intData->GetValue());
newString = "i" + newString.append(ss.str()) + "e";
} //can't reach here
//use assert!
if (baseData->GetDataType() == B_LIST)
{
newString.append("l");
ListData* listData = static_cast<ListData*>(baseData);
list<BaseData*> newList = listData->GetValue();
for(list<BaseData*>::iterator it = newList.begin(); it != newList.end(); it++)
{
newString.append(EncodeToStr(*it));
}
newString.append("e");
} if (baseData->GetDataType() == B_STR)
{
StrData* strData = static_cast<StrData*>(baseData);
stringstream ss;
ss << (strData->GetValue()).length();
//string newStr = strData->GetValue();
newString = newString + ss.str() + ":" + (strData->GetValue());
} return newString;
}
当然好的B编码必然对应好的数据结构,因为不管网络中传输的数据是什么样子的,
我们必须要在自己的程序内管理好数据结构才行,因为我们要在map中放入还不能
确定数据类型的数据,所以需要在map中放入父类实例的指针,以让我们能够在以后
还能通过指针使用多态的特性让子类去代替父类,看看代码理解下,当然这里使用了
几个面向对象语言的高级特性:设计模式中的composit模式,STL以及多态。大家
可以去了解下,当然这部分的代码绝大部分是我群里的“元古”大神所实现的数据结构。
代码和类图都在下面贴出来:
#ifndef USEING
#define USEING #include <string>
#include <list>
#include <map>
#include<assert.h>
#include <iostream>
#include <set>
using namespace std; typedef enum BeType
{
B_STR,
B_INT,
B_LIST,
B_DIC,
}BeType; class BaseData
{
public:
BeType beType; public:
//stick to polymorphisms
//virtual ~BaseData();
virtual BeType GetDataType()
{
return beType;
};
virtual void SetDataType(BeType beType){}; }; class StrData : public BaseData
{
private:
string sValue;
public:
StrData():sValue("")
{
SetDataType(B_STR);
};
BeType GetDataType()
{
return beType;
}
void SetDataType(BeType newBeType)
{
beType = newBeType;
} string GetValue() {return sValue; };
void SetValue(char byte) {sValue.append(&byte, ); };
void SetValue(char* bytes)
{
int length = sizeof(bytes);
sValue.append(bytes, length);
};
void SetValue(string str)
{
sValue.append(str);
};
}; class IntData : public BaseData
{
private:
int iValue;
public:
IntData():iValue()
{
SetDataType(B_INT);
}
BeType GetDataType()
{
return beType;
}
void SetDataType(BeType newBeType)
{
beType = newBeType;
} int GetValue()
{
return iValue;
}
void SetValue(char* numStr)
{
iValue = atoi(numStr);
}
void SetValue(int num)
{
iValue = num;
}
}; class ListData : public BaseData
{
private:
//there is a strong relationship between ListData and BaseData, it is named "compose" , it is also a design pattern!
list<BaseData*> lValue;
public:
ListData():lValue()
{
SetDataType(B_LIST);
};
BeType GetDataType()
{
return beType;
}
void SetDataType(BeType newBeType)
{
beType = newBeType;
} list<BaseData*> GetValue()
{
return lValue;
}
void add(BaseData* baseData)
{
lValue.push_back(baseData);
}
//destructor! it will be called when user use the "delete" key, and inner ListData , BaseData will also call delete!
~ListData()
{
for(list<BaseData*>::iterator it = lValue.begin(); it != lValue.end(); it++)
{
if(*it != NULL)
delete *it;
}
lValue.clear();
}
}; class DicData : public BaseData
{
private:
//the reason to use the pointer,because we need to transfer the father to the son, it is polymorphisms
//remember
map<string, BaseData*> dValue; public:
//no initialize for map
DicData()
{
SetDataType(B_DIC);
};
BeType GetDataType()
{
return beType;
}
void SetDataType(BeType newBeType)
{
beType = newBeType;
} map<string, BaseData*> GetValue()
{
return dValue;
}
//except the second value, we can also insert some else value like string to StrData, int to IntData, list to ListData!
void add(string str, BaseData* baseData)
{
dValue.insert(make_pair(str, baseData));
}
void add(char* bytes, BaseData* baseData)
{
dValue.insert(make_pair(bytes, baseData));
}
void add(string strOne, string strTwo)
{
StrData* strData = new StrData();
strData->SetValue(strTwo);
dValue.insert(make_pair(strOne, strData));
}
void add(string str, char* bytes, int lengh)
{
string tempStr = "";
tempStr.append(bytes, lengh);
add(str, tempStr);
}
~DicData()
{
for(map<string, BaseData*>::iterator it = dValue.begin(); it != dValue.end(); it++)
{
//attention the different between the map and list
//the iterator is a pointer, but we've used the ->
if(it->second != NULL)
delete it->second;
}
dValue.clear();
}
};
#endif
类图:
到此处为止,除了协议本身的实现没有贴出来,最终的辅助程序都贴出来了。
至于peer之间交互信息的代码就不贴出来,有了几个重要的辅助程序,基本上
稍微写写大概也能写出来。
然后我就开始跑我的DHT爬虫,每次跑起来总是等不到我想要的infohash,
我从头到位debug了下,发现我是能够接收到数据的,但是数据总是不能被我的
BCode正确解码,我跟踪了下接受数据,发现了这个状况:
这里只取出来了部分数据,里面不仅有负值的ASC2数据,还有'\0'!!
于是cout出来呈现这样:
我的代码中是要把char*赋值给string的,结果竟然有'\0'!并且'\0'就是协议本身允许的能够传输的数据!
在赋值中直接就把我网络字节流给截断了!我惊觉我的所有BCode都不能再用了,因为统统都是string构成的
基本元素!不仅如此,我还意识到传输中一大堆负值是怎么回事??
于是我找了个能跑的DHT爬虫,也一步步跟踪了下,发现ASC2的负值都是能够传输的,也是符合协议的,
最奇葩的是竟然还能被解析成功,这样的数据打印都打印不出来啊...太坑了,不过也扩展了见识,以下是对比图:
现在终于发现不能解析的原因了,而许多全面向对象的语言就不存在此问题,是因为像c#、Java或者
Python这样的语言中的string根本就不是默认'\0'为结尾的...
所以现在需要改下我BCode中string部分,全部替换为char*,可惜了如此漂亮的递归啊!
---恢复内容结束---