| 各个模块的设计和实现 |
4.1 种子解析模块的设计和实现
解析种子文件主要在parse_metafile.h和parse_metafile.c中完成。parse_metafile.h文件的内容为:
parse_metafile.h
#ifndef PARSE_METAFILE
#define PARSE_METAFILE
// 保存从种子文件中获取的tracker的URL
typedef struct _Announce_list {
} Announce_list;
// 保存各个待下载文件的路径和长度
typedef struct _Files {
} Files;
int read_metafile(char *metafile_name);
int find_keyword(char *keyword,long *position);
int read_announce_list();
int add_an_announce(char* url);
int get_piece_length();
int get_pieces();
int is_multi_files();
int get_file_name();
int get_file_length();
int get_files_length_path();
int get_info_hash();
int get_peer_id();
void release_memory_in_parse_metafile();// 释放parse_metafile.c中动态分配的内存
int
#endif
以下是parse_metafile.c文件的头部,主要是包含了一些头文件和定义一些全局变量,各个函数的定义将在后面列出。
parse_metafile.c
#include <stdio.h>
#include <ctype.h>
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "parse_metafile.h"
#include "sha1.h"
char
long
int
char
int
int
char
long long file_length = 0;
Files
unsigned
unsigned
Announce_list *announce_list_head = NULL;
下面对解析种子文件中用到函数功能解释如下。
ul
参数:metafile_name是种子文件名。
返回:处理成功返回0,否则返回−1
附注:将种子文件的内容读入到全局变量metafile_content所指向的缓冲区中以方便处理。该函数的实现代码为:
int read_metafile(char *metafile_name)
{
#ifdef DEBUG
#endif
}
函数代码说明。
(1)编译器预定义的宏__FILE__和__LINE__在程序中可以直接使用。__FILE__代表该宏所在的源文件的文件名,在源文件parse_metafile.c中该宏的值等于“parse_metafile.c”,宏__LINE__的值为__LINE__所在行的行号。
(2)种子文件必须以二进制的方式打开,否则如果以字符方式打开可能无法读取整个文件的内容。无法读取的原因在于piece的hash值中可能含有字符0x00,若文件以字符形式打开,遇到该字符,库函数就认为文件已经结束。
(3)增加“#ifdef DEBUG
ul int find_keyword(char *keyword,long *position)
参数:keyword为要查找的关键字,position用于返回关键字第一个字符所在的下标。
返回:成功执行并找到关键字返回1,未找到返回0,执行失败返回−1。函数实现代码如下所示:
int find_keyword(char *keyword,long *position)
{
}
函数代码说明。
该函数在种子文件解析模块的源文件parse_metafile.c中被频繁使用,用于查找某些关键字。例如,关键字“8:announce”和“13:announce-list”之后都是Tracker服务器的地址,找到该关键字后,便可以获取Tracker的地址。
ul read_announce_list()
功能:获取Tracker地址,并将获取的地址保存到全局变量announce_list_head指向的链表中。该函数实现代码如下:
int read_announce_list()
{
#ifdef DEBUG
#endif
}
程序说明。
(1)下面是某种子文件开头的一部分,请对照它来理解read_announce_list函数
d8:announce32:http://tk.greedland.net/announce13:announce-listll32:http://tk.greedland.net/announceel33:http://tk2.greedland.net/announceee...
第一个字符‘d’是B编码中字典的起始符,接着是关键字“8:announce”,该关键字是一个长度为8的字符串,其对应的值为长度为32的字符串“32:http://tk.greedland.net/announce”,它是一个Tracker服务器的URL,接着是关键字“13:announce-list”,该关键字对应的值是一个列表,因为关键字“13:announce-list”之后的第一个字符为列表的起始字符‘l’,该列表中含有两个元素,这两个元素的类型也都是列表。
如果有关键字“13:announce-list”就不用处理关键字“8:announce”的原因在于,前者对应的值中必定包含后者对应的值。
(2)“#ifdef DEBUG”和“#endif”之间的语句用于打印各个Tracker的URL。
ul int add_an_announce(char *url)
功能:连接某些Tracker时会返回一个重定向的URL,需要连接该URL才能获取peer。函数实现代码如下:
int add_an_announce(char *url)
{
Announce_list *p = announce_list_head, *q;
}
ul
功能:判断是下载多个文件还是单文件,若含有关键字“5:files”则说明下载的是多个文件。函数实现的代码如下:
int is_multi_files()
{
#ifdef DEBUG
#endif
}
ul int get_piece_length()
功能:获取piece的长度。函数实现的代码如下:
int get_piece_length()
{
#ifdef DEBUG
#endif
}
程序说明。
以下是某种子文件的一部分:12:piece lengthi262144e6:pieces16900:...
从中可以看到,关键字“12:piece length”后面跟一个B编码的整型数(以i作为起始字符,以e作为终结字符)。262144(256K),说明每个piece的长度都是256KB(最后一个piece除外)。接着是关键字“6:pieces”,它对应的值是一个B编码的字符串,存放各个piece的hash值,16900是字符串的长度,该字符串长度除以20即为piece数,因为每个piece的hash值为固定的20字节。
ul get_pieces()
功能:获取每个piece的hash值,并保存到pieces所指向的缓冲区中。函数实现的代码如下:
int get_pieces()
{
#ifdef DEBUG
#endif
}
ul
功能:获取待下载的文件的文件名,如果下载的是多个文件,则获取的是目录名。函数实现的代码如下:
int get_file_name()
{
#ifdef DEBUG
#endif
}
程序说明。
以下是一个完整的较为简单的种子文件:
d8:announce32:http://tk.greedland.net/announce13:announce-listll32:http://tk.greedland.net/annou
nceel33:http://tk2.greedland.net/announceee13:creation datei1187968874e4:infod6:lengthi119861
306e4:name31:[ymer][naruto][246][jp_cn].rmvb10:name.utf-831:[ymer][naruto][246][jp_cn].rmv
b12:piece lengthi262144e6:pieces9160:...ee
关键字“13:creation date”之前的部分已经在介绍read_announce_list函数时分析过了,此处不再赘述。关键字“13:creation date”及其对应的值“i1187968874e”,它指明了创建种子文件的时间。我们注意到时间是一个整数,它是自1970年1月1日到种子文件创建时所经过的秒数,Linux中有专门的库函数处理这种表示类型的时间。
关键字“4:info”对应的值是一个字典,因为该关键字之后的第一个字符是B编码中字典的起始符‘d’,与该起始符对应的终止符是文件末尾的倒数第二个‘e’。计算info_hash时,就是以关键字“4:info”对应的值作为输入,计算其hash值,将得到的值作为info_hash。文件末尾最后一个字符‘e’与文件开头的’d’对应,因此整个种子文件就是一个B编码的字典。
关键字“6:length”对应的值是待下载文件的长度,以字节为单位,可以大致地确定待下载文件的长度为119MB。
关键字“4:name”对应的值为待下载的文件的文件名,在这个种子文件中没有关键字“5:files”说明待下载的是单文件。
关键字“10:name.utf-8”对应的值也是待下载文件的文件名,只不过以UTF-8的形式表示,UTF-8的形式可以表示宽字符,即中文、日文、朝鲜文等字符。
ul int get_file_length()
功能:获取待下载文件的长度。函数实现的代码如下:
int get_file_length()
{
#ifdef DEBUG
#endif
}
ul get_files_length_path()
功能:对于多文件,获取各个文件的路径以及长度。函数实现的代码如下:
int get_files_length_path()
{
}
程序说明。
图13-2是一个多文件种子的一部分,可以参照图13-2理解get_files_length_path函数。
多文件种子的关键字“5:files”对应的值是比较复杂的。关键字“5:files”说明这是一个多文件种子,它对应的值是一个列表,列表的每个元素是字典,每个字典代表一个待下载文件。
图13-2
“5:filesl”及字符‘d’之后,有一个关键字“6:length”及其值“i127025815e”,然后是关键字“4:path”,其值为一个列表“l34:[BBsee出品][军情观察室08.22].rmvbe”,“rmvbee”中最后一个‘e’与字符‘d’对应。
然后“d6:lengthi76e4:pathl42:综艺 美剧 篮球 足球 尽在迅视XunTv.Net.urlee”又是一个字典。“urleee”中最后一个‘e’与“5:files”后的‘l’构成一个列表。“4:name”所跟的是目录名,然后是“12:piece length”关键字,“6:pieces”关键字。
从中可以总结出:有一个目录名“[BBsee出品][军情观察室08.22]”,其中存放了两个文件“[BBsee出品][军情观察室08.22].rmvb”和“综艺 美剧 篮球 足球 尽在迅视 XunTv. Net.url”,长度分别为127025815字节和76字节。
ul int get_info_hash ()
功能:计算info_hash的值。函数实现代码如下:
int get_info_hash()
{
else
} else if((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {
} else if(metafile_content[i] == 'e') {
#ifdef DEBUG
#endif
}
程序说明。
(1)在种子文件解析模块,由种子文件的内容计算info_hash的值是比较复杂的。前面已经提到,由关键字“4:info”对应的值来计算info_hash,该关键字对应的值是一个B编码的字典,问题的关键在于找到与“4:info”之后的‘d’对应的‘e’。
get_info_hash函数中找到所需要的‘e’的思路是:在“4:info”之后,每当遇到字典的起始符‘d’,则将push_pop的值加1(push_pop初始值为0),遇到列表的起始符‘l’也作相同处理;遇到整数的起始符‘i’则一直扫描直到找到与之对应的终结符‘e’:遇到一个0~9的数字说明接下来是一个字符串,跳过该字符串继续扫描;遇到‘e’则将push_pop值减1,如果减1后,push_pop值为0,说明已经找到了与‘d’匹配的‘e’。其思路类似于使用数据结构中的“栈”进行括号匹配操作。
(2)以“SHA1”开头的变量和函数用于计算一段文本的hash值,这些变量和函数的定义在sha1.h和sha1.c文件中,hash值的计算原理不必深究。计算hash值的这段代码的功能是以metafile_content[begin]~metafile_content[end]这end-begin+1个字符作为输入,计算其hash值,并把结果保存到info_hash所指向的数组中。
ul int get_peer_id()
功能:生成一个惟一的peer id。函数实现代码如下:
int get_peer_id()
{
// peer_id前8位固定为-TT1000-
#ifdef DEBUG
#endif
}
ul void release_memory_in_parse_metafile()
功能:释放动态申请的内存。函数实现代码如下:
void release_memory_in_parse_metafile()
{
}
ul int parse_metafile(char *metafile)
功能:调用parse_metafile.c中定义的函数,完成解析种子文件。该函数由main.c调用。
返回:解析成功返回0,否则返回-1。函数实现代码如下:
int parse_metafile(char *metafile)
{
}