基于Composite模式实现BitTorrent文件解析
来源:互联网 发布:相宜本草 面膜 知乎 编辑:程序博客网 时间:2024/05/22 12:57
基于Composite模式实现BitTorrent文件解析
.torrent文件中的元素
BitTorrent是当前最为流行的文件共享工具,在www.bittorrent.com网站上,给出了BitTorrent所使用的协议;在该文档中,描述了.torrent文件的编码格式:bencode,参见rfc2047。据此文档描述,.torrent文件的元素类型为:
字符串(String):以数字开始,该数字为字符个数,在字符和数字之间用“:”分隔。如:4:spam表示“spam”。
整数:以“i”开始,以“e”结束,中间为数值。如:i3e表示3。
列表:以“l”开始,以“e”结束,中间为列表值。如:l4:spam4eggse表示[‘spam’, ‘eggs’]。
字典:以“d”开始,以“e”结束,中间为键、值对。如:d3:cow3:moo4:spam4eggse表示{‘cow’:’moo’, ‘spam’:’eggs’}。
其中列表和字典可以包含其他元素类型,如字典的值可以是列表,列表的值可以是字典。正是鉴于.torrent元素间的特征,才选用Composite模式。
基于Composite模式的设计
基于Composite模式和.torrent文件的实际情况,可以得到如图的设计类图。
DictionaryHandler和ListHandler是复合体,其特征是具有容器。
Handler是接口类(为实现方便,把一些简单公共功能在基类中实现)。
class Handler
{
public:
Handler(void);
Handler(ContentType type) : m_type(type){}
virtual ~Handler(void);
virtual int handle(istream& is) = 0;
virtual void get_result(ostream& os) const = 0;
ContentType get_type() const;
protected:
//this method will verify if the next charactor in the file stream is 'c';
//if so, it will move one charactor in the file stream.
void check_one_char(istream& is, char c);
ContentType m_type;
};
类IntegerHandler实现了这些接口,并提供返回结果的方法:
class IntegerHandler : public Handler
{
public:
IntegerHandler() : Handler(NUM){}
virtual int handle(istream& is);
virtual void get_result(ostream& os) const;
int get_value() const;
private:
int m_value;
};
同样,StringHandler也实现了这些接口:
class StringHandler : public Handler
{
public:
StringHandler() : Handler(STRING){}
virtual int handle(istream& is);
virtual void get_result(ostream& os) const;
string get_value() const;
private:
string m_value;
};
现在看复合体定义的不同之处:容器。
class ListHandler : public Handler
{
public:
ListHandler() : Handler(LIST){}
virtual ~ListHandler();
virtual int handle(istream& is);
virtual void get_result(ostream& os) const;
//typedef list<Handler*> ItemList
ItemList get_value() const;
private:
//container, which used for composite pattern
ItemList m_items;
};
class DictionaryHandler : public Handler
{
public:
DictionaryHandler() : Handler(DICT){}
virtual ~DictionaryHandler();
virtual int handle(istream& is);
virtual void get_result(ostream& os) const;
//typedef map<string, Handler*> DictMap;
DictMap get_value() const;
private:
//container, which used for compositepattern
DictMap m_dicts;
};
以DictionaryHandler为例说明,Composite模式中如何在复合体中包含其他元素。在字典元素中,键总是字符串(否则就不是字典了);
所以总是:
1)以StringHandler先试图读取键值,如果有异常抛出,则可能遇到了其他类型的元素,或者结束该字典;这样处理的好处是:此代码同样能处理值。
如果捕获异常,现将文件流回滚;检查下一个字符:
2)如果是’e’,字典结束。
3)如果是’l’,表明列表开始,新建一个ListHandler来获取下一个元素信息;记录该对象到容器中;
4)如果是’i’,表明数字开始,新建一个IntegerHandler来获取下一个元素信息;记录该对象到容器中;
5)如果是’d’,表明数字开始,新建一个DictionaryHandler来获取下一个元素信息;记录该对象到容器中;
6)否则按照字符串处理,新建一个StringHandler来获取下一个元素信息;记录该对象到容器中;
以1)到6)反复执行,直到遇到’e’结束。
7)最后,返回从文件流中处理字符串的个数。
下图为其字典元素的调用时序:
源代码
这里给出DictionaryHandler和ListHandler实现代码:
int DictionaryHandler::handle(istream& is)
{
int pos_start = is.tellg();
check_one_char(is, 'd');
while(!is.eof())
{
#ifdef _DEBUG
cout << "now handle:" << hex << uppercase <<is.peek() << endl;
#endif
//the key of a dictionary is always a string
//try to read a string key out of the file stream
//if failed, then set the current position in the file stream back
//then check if another content type is encountered.
string key = "";
StringHandler handler;
int pos_cur = is.tellg();
try
{
handler.handle(is);
key = handler.get_value();
}
catch(BitTorrentParserException& be)
{
#ifdef _DEBUG
cout << be.what() << endl;
#endif
//set the position back
//this always happens at the end of the dictionary content
//or another list for the key starts
is.seekg(pos_cur, ios::beg);
is.clear();
}
//the same as the list handler
char c = is.peek();
if(c == 'e')
{
Handler::check_one_char(is, c);
break;
}
else if(c == 'l')
{
ListHandler* handler = new ListHandler();
handler->handle(is);
m_dicts.insert(make_pair(key, handler));
}
else if(c == 'd')
{
DictionaryHandler* handler = new DictionaryHandler();
handler->handle(is);
m_dicts.insert(make_pair(key, handler));
}
else if(c == 'i')
{
IntegerHandler* handler = new IntegerHandler();
handler->handle(is);
m_dicts.insert(make_pair(key, handler));
}
else
{
StringHandler* handler = new StringHandler();
handler->handle(is);
m_dicts.insert(make_pair(key, handler));
}
}
return static_cast<int>(is.tellg()) - pos_start;
}
//this method will extract between 'l' and 'e'
//@param, is, input file stream
//@return, the charactor read.
int ListHandler::handle(istream& is)
{
//record the first position in the file stream
int pos_start = is.tellg();
//bypass the 'l'
is.ignore(1, 'l');
while(!is.eof())
{
//check the content type
char c = is.peek();
if(c == 'e')
{
//if 'e', then end the list
Handler::check_one_char(is, c);
break;
}
else if(c == 'd')
{
//if 'd', the new a DictionaryHandler to handle the following content
DictionaryHandler* handler = new DictionaryHandler();
handler->handle(is);
m_items.push_back(handler);
}
else if(c == 'i')
{
//if 'd', the new a IntegerHandler to handle the following content
IntegerHandler * handler = new IntegerHandler();
handler->handle(is);
m_items.push_back(handler);
}
else if(c == 'l')
{
//if 'd', the new a ListHandler to handle the following content
ListHandler* handler = new ListHandler();
handler->handle(is);
m_items.push_back(handler);
}
else
{
//all other situation, use a String Handler to get the content
StringHandler * handler = new StringHandler();
handler->handle(is);
m_items.push_back(handler);
}
}
return static_cast<int>(is.tellg()) - pos_start;
}
//@param, is, input file stream
//@return, the charactor read.
int ListHandler::handle(istream& is)
{
//record the first position in the file stream
int pos_start = is.tellg();
//bypass the 'l'
is.ignore(1, 'l');
while(!is.eof())
{
//check the content type
char c = is.peek();
if(c == 'e')
{
//if 'e', then end the list
Handler::check_one_char(is, c);
break;
}
else if(c == 'd')
{
//if 'd', the new a DictionaryHandler to handle the following content
DictionaryHandler* handler = new DictionaryHandler();
handler->handle(is);
m_items.push_back(handler);
}
else if(c == 'i')
{
//if 'd', the new a IntegerHandler to handle the following content
IntegerHandler * handler = new IntegerHandler();
handler->handle(is);
m_items.push_back(handler);
}
else if(c == 'l')
{
//if 'd', the new a ListHandler to handle the following content
ListHandler* handler = new ListHandler();
handler->handle(is);
m_items.push_back(handler);
}
else
{
//all other situation, use a String Handler to get the content
StringHandler * handler = new StringHandler();
handler->handle(is);
m_items.push_back(handler);
}
}
return static_cast<int>(is.tellg()) - pos_start;
}
//this method will extract the number before ':' and then read the string
//@param, is, input file stream
//@return, the charactor read.
int StringHandler::handle(istream& is)
{
int pos_start = is.tellg();
char buffer[32];
memset(buffer, 0, sizeof(buffer));
//@param, is, input file stream
//@return, the charactor read.
int StringHandler::handle(istream& is)
{
int pos_start = is.tellg();
char buffer[32];
memset(buffer, 0, sizeof(buffer));
//get number before ':'
is.get(buffer, sizeof(buffer) - 1, ':');
int content_length = atoi(buffer);
//never less than 0
if(content_length <= 0&& buffer[0] != '0')
{
throw BitTorrentParserException(string(buffer), is.tellg(), "number is expected.");
}
if(content_length > 0)
{
//read the string
m_value.resize(content_length);
char* temp = new char[content_length + 1];//plus '/0'
is.ignore(1, ':'); //:
//get the content
is.read(temp, content_length);
m_value.assign(temp, content_length);
delete temp;
}
else
{
//for null string
is.ignore(1, ':'); //:
m_value = "";
}
return static_cast<int>(is.tellg()) - pos_start;
}
is.get(buffer, sizeof(buffer) - 1, ':');
int content_length = atoi(buffer);
//never less than 0
if(content_length <= 0&& buffer[0] != '0')
{
throw BitTorrentParserException(string(buffer), is.tellg(), "number is expected.");
}
if(content_length > 0)
{
//read the string
m_value.resize(content_length);
char* temp = new char[content_length + 1];//plus '/0'
is.ignore(1, ':'); //:
//get the content
is.read(temp, content_length);
m_value.assign(temp, content_length);
delete temp;
}
else
{
//for null string
is.ignore(1, ':'); //:
m_value = "";
}
return static_cast<int>(is.tellg()) - pos_start;
}
//this method will extract the charactors between 'i' and 'e'
//@param, is, input file stream
//@return, the charactor read.
//@param, is, input file stream
//@return, the charactor read.
int IntegerHandler::handle(istream& is)
{
int pos_start = is.tellg();
char buffer[32];
memset(buffer, 0, sizeof(buffer));
{
int pos_start = is.tellg();
char buffer[32];
memset(buffer, 0, sizeof(buffer));
is.ignore(1, 'i');
is.get(buffer, sizeof(buffer) - 1,'e');
is.ignore(1, 'e');
m_value = atoi(buffer);
//always not less than 0
if(m_value < 0)
{
throw BitTorrentParserException(string(buffer), pos_start, "value is less than zero");
}
return static_cast<int>(is.tellg()) - pos_start;
}
is.get(buffer, sizeof(buffer) - 1,'e');
is.ignore(1, 'e');
m_value = atoi(buffer);
//always not less than 0
if(m_value < 0)
{
throw BitTorrentParserException(string(buffer), pos_start, "value is less than zero");
}
return static_cast<int>(is.tellg()) - pos_start;
}
测试结果
如下为对“[TBox] Crash Test Dummies.torrent”(从www.bittorrent.com上下载的一个测试文件,以test搜索)的解析结果:
announce http://tracker.torrentbox.com:2710/announce
announce-list {{udp://tracker.torrentbox.com:2710/announce}{http://tracker.torrentbox.com:2710/announce}{http://redir.bthub.com/redir.php?hash=50c9e8d5fc98727b4bbc93cf5d64a68db647f04f}}
azureus_properties dht_backup_enable 1
comment
comment.utf-8
created by Azureus/2.3.0.4
creation date 1126823734
encoding UTF-8
info ed2k F7B6B02FFB7EA6ED8F6E39113492F89
files {ed2k 8F142AEA7564F040DEC6742EDC6DA4FA
length 2512770
path {Crash Test Dummies - Androgynous.mp3}
path.utf-8 {Crash Test Dummies - Androgynous.mp3}
sha1 5CAFF7DD717645277F39F41FAA5923D6BCDA1FF
ed2k E27D8CED529F79E32148FE0A24E4AAC
length 3879079
path {Crash Test Dummies - At My Funeral.mp3}
path.utf-8 {Crash Test Dummies - At My Funeral.mp3}
sha1 D0B9FFDD923B5E5D63EF0D07E59D6E5CB2FE74A
ed2k 4FB3BA5D10494CD9718E52C362FE9EB
…其他文件的列表(略)
name Crash Test Dummies
name.utf-8 Crash Test Dummies
piece length 65536
pieces 8B7F608981BCE9403AB9C8AC4DCE9D6959898264207E455AD320A762D462A9261954BF6A7329797BCA2C98156B234E49C08446A6638A…(Hash值,略)
private 0
sha1 89DE5AD6F359524847BB9EC237812B44569637
modified-by {TorrentBox.com}
小结
解析.torrent文件是实现Bittorrent的第一步,这里基于Composite模式给出了优化的解决方案;希望能对程序员提供有益的帮助;另外,程序中大量使用了文件流的处理,对流的使用有指导意义。
- 基于Composite模式实现BitTorrent文件解析
- C#实现BT种子文件(BitTorrent)解析
- Python边学边用--BT客户端实现之BitTorrent文件解析
- 设计模式解析和实现(C++)之八-Composite模式
- 利用python的BitTorrent扩展库,解析BitTorrent文件
- C++实现Composite模式
- python实现Composite模式
- Composite 模式的实现
- Python脚本解析BitTorrent种子文件内容
- BitTorrent种子文件的解析+(2)
- BitTorrent种子文件的解析+(1)
- BitTorrent种子文件的解析 (1)
- BitTorrent种子文件的解析 (2)
- 一个简单的BitTorrent客户端实现(二):种子文件解析及信息保存
- Linux平台下基于BitTorrent应用层协议的下载软件开发--种子文件解析模块(parse_metafile.c)
- Linux平台下基于BitTorrent应用层协议的下载软件开发--种子文件解析模块(parse_metafile.h)
- C++实现Structural - Composite模式
- C++实现Structural - Composite模式
- 请不要做浮躁的嵌入式系统工程师
- DataGrid技巧大集合
- 三言两语。
- Delphi7实现B/S应用的注意事项
- 关于系统响应函数OnSysCommand及托盘Shell_NotifyIcon()等
- 基于Composite模式实现BitTorrent文件解析
- FastReport问题集 选自Delphi园地
- Gallery2里PHP支持gettext
- 利用 gettext 来实现 PHP 的国际化编程
- 毕业生的来清醒一下
- 女人,呵呵...让人意乱情迷
- 利用 NDIS 中间层驱动程序截获网络封包
- Hook ZwQuerySystemInformation 隐藏qq程序
- icesword 驱动部分分析