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*,可惜了如此漂亮的递归啊!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

---恢复内容结束---

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。