DHT协议C++实现过程中各种问题
---恢复内容开始---
博主是个菜鸟,什么地方写的不对或者理解有误的地方还请指正出来。
DHT协议是BT协议中的一部分,也是一个辅助性的协议。HTTP协议中用
来定位资源也就是html文本是用URL这样的协议的,而在BT或者说P2P的
世界中,没有了以前那样可以直接定位的服务器,所以需要能够动态的掌
握到资源的分布,那DHT协议就是BT中用来定位资源的协议,具体的不多
说,可以看看官方网站对于BT或者DHT十分详尽的描述:
http://www.bittorrent.org/beps//bep_0003.html
或者去看看其他人翻译出来的文章理解理解:
http://blog.csdn.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编码程序,可以跑起来,但是有一个致命的问题存在,容后再说,先贴
代码:
1 #define USING 2 #include "DataStruction.h" 3 #include <string> 4 using namespace std; 5 6 /* 7 https://github.com/CreateChen/Bencode 8 transfer the CreateChen‘s c# code into c++, the thought is very impressive! 9 */ 10 11 12 enum EncodeState 13 { 14 KEY, 15 VALUE, 16 }; 17 18 class BCode 19 { 20 private: 21 static int index; 22 static BaseData* RealDecodeToDic(string str, int& index, EncodeState state);24 public: 25 static BaseData* DecodeToDic(string str);27 static string EncodeToStr(BaseData*); 28 };
#include "BCode.h" #include <sstream> int BCode::index = 0; //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(‘0‘ < c && c <= ‘9‘) { string returnString = ""; string contentString = ""; while(str[index] != ‘:‘) { returnString += str[index]; index++; } int stringLength = atoi(returnString.c_str()); for (int i = 0; i < stringLength; i++) { contentString += str[index + 1]; 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, 1); }; 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(0) { 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(0) { 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*,可惜了如此漂亮的递归啊!
---恢复内容结束---
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。