Ctorrent源码版本【ctorrent-dnh3.3.2】
URL:【http://www.rahul.net/dholmes/ctorrent/ctorrent-dnh3.3.2.tar.gz】
目的:工作接触BT软件的二次开发,顺便记录一下开源工具Ctorrent的原码的阅读过程
书接上一章,前面介绍了Ctorrent制作种子文件的具体步骤,已经对部分重点函数做了详细解释,接下来,将继续介绍Ctorrent是如何根据种子进行下载任务。
Ctorrent按序通过BTCONTENT.InitialFromMI,WORLD.Initial_ListenPort,Tracker.Initial和Downloader这四个函数进行下载任务。
先看BTCONTENT.InitialFromMI函数,该函数主要是解析种子文件中数据,具体注解如下代码块【1】:
/*
const char *metainfo_fname 种子文件的文件指针
const char *saveas BT下载时是否另存到某一个目录下,可以为空
*/
int btContent::InitialFromMI(const char *metainfo_fname,const char *saveas)
{
#define ERR_RETURN() {if(b) delete []b; return -1;}
unsigned char *ptr = m_shake_buffer;
char *b;
const char *s;
size_t flen, q, r;
int tmp;
m_cache_hit = m_cache_miss = m_cache_pre = 0;
time(&m_cache_eval_time);
/*
b 种子文件的文件指针
flen 种子文件有效长度
*/
b = _file2mem(metainfo_fname,&flen);
if ( !b ) return -1;
// announce
/*
m_announce变量 存放主Tracker的URL
meta_str宏函数,获取key值为"announce"的value
ps meta_str,meta_pos和meta_int宏函数实际调用的是decode_query这个函数,后续针对这个函数做详
细介绍
*/
if( !meta_str("announce",&s,&r) ) ERR_RETURN();
if( r > MAXPATHLEN ) ERR_RETURN();
m_announce = new char [r + 1];
memcpy(m_announce, s, r);
m_announce[r] = '\0';
/*
种子文件除了有主tracker地址,还会有多个从tracker地址
m_announcelist[n] 数组存放多个从tracker地址
*/
// announce-list
if( r = meta_pos("announce-list") ){
const char *sptr;
size_t slen, n=0;
if( q = decode_list(b+r, flen-r, (char *)0) ){
int alend = r + q;
r++; // 'l'
for( ; r < alend && *(b+r) != 'e' && n < 9; ){ // each list
if( !(q = decode_list(b+r, alend-r, (char *)0)) ) break;
r++; // 'l'
for( ; r < alend && n < 9; ){ // each value
if( !(q = buf_str(b+r, alend-r, &sptr, &slen)) )
break; // next list
r += q;
if( strncasecmp(m_announce, sptr, slen) ){
m_announcelist[n] = new char[slen+1];
memcpy(m_announcelist[n], sptr, slen);
(m_announcelist[n])[slen] = '\0';
n++;
}
}
r++; // 'e'
}
}
}
if( meta_int("creation date", &r) ) m_create_date = (time_t) r;
if( meta_str("comment", &s, &r) && r ){
if( m_comment = new char[r + 1] ){
memcpy(m_comment, s, r);
m_comment[r] = '\0';
}
}
if( meta_str("created by", &s, &r) && r ){
if( m_created_by = new char[r + 1] ){
memcpy(m_created_by, s, r);
m_created_by[r] = '\0';
}
}
// infohash
if( !(r = meta_pos("info")) ) ERR_RETURN();
if( !(q = decode_dict(b + r, flen - r, (char *) 0)) ) ERR_RETURN();
Sha1(b + r, q, m_shake_buffer + 28);
// private flag
if( meta_int("info|private", &r) ) m_private = r;
// hash table
/*
m_hashtable_length 整个种子文件的sha1值长度,由于sha1的计算值必须是20字节长度,
所以m_hashtable_length 必须时20的整数倍
*/
if( !meta_str("info|pieces",&s,&m_hashtable_length) ||
m_hashtable_length % 20 != 0 ) ERR_RETURN();
/*
m_hash_table 存放种子文件的sha1哈希值
*/
if( !arg_flg_exam_only ){
m_hash_table = new unsigned char[m_hashtable_length];
#ifndef WINDOWS
if( !m_hash_table ) ERR_RETURN();
#endif
memcpy(m_hash_table, s, m_hashtable_length);
}
/*
m_npieces 存放种子文件有效片数(piece)
sha1值固定是20个字节,所以m_hashtable_length / 20结果就是整个文件片数
*/
if( !meta_int("info|piece length",&m_piece_length) ) ERR_RETURN();
m_npieces = m_hashtable_length / 20;
/*
cfg_req_slice_size 定义了BT每一次下载的真正单位
*/
if( m_piece_length < cfg_req_slice_size )
cfg_req_slice_size = m_piece_length;
cfg_req_queue_length = (m_piece_length / cfg_req_slice_size) * 2 - 1;
/*
BuildFromMI 功能就是解析文件下载的本地目录数据,具体细节面有描述
*/
if( m_btfiles.BuildFromMI(b, flen, saveas) < 0 ) ERR_RETURN();
delete []b;
b = (char *)0;
if( arg_flg_exam_only ){
PrintOut();
return 0;
}else{
arg_flg_exam_only = 1;
PrintOut();
arg_flg_exam_only = 0;
}
/*
CreateFiles函数根据BuildFromMI函数解析的值,构建真正的文件下载的本地目录数据,具体细节面有描述
*/
if( (tmp = m_btfiles.CreateFiles()) < 0 ) ERR_RETURN();
r = tmp;
.
.
.
该函数比较大,后续的部分,后面会继续解析
.
.
.
上述代码中,提到了decode_query这个函数,这个函数在bendode.cpp文件中,这个文件是制作和解析BT文件(bencode格式)的主要文件,具体的解析bencode功能如下代码块【2】:
#include "./def.h"
#include "bencode.h"
#ifndef WINDOWS
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef HAVE_SNPRINTF
#include "compat.h"
#endif
static const char* next_key(const char *keylist)
{
for(;*keylist && *keylist != KEY_SP; keylist++);
if(*keylist) keylist++;
return keylist;
}
static size_t compare_key(const char *key,size_t keylen,const char *keylist)
{
// keylen是*key中有效字符串长度,用来当作和目标*keylist中的字符串进行逐个字符比较时边界
for(;keylen && *keylist && *key==*keylist;keylen--,key++,keylist++) ;
if(!keylen) if(*keylist && *keylist!=KEY_SP) return 1;
return keylen;
}
/*
const char *b const类型BT文件指针
size_t len BT文件剩余长度
char beginchar 字符串开始的标志位(BT文件常用的标志为 整数开头【i】 list列表【l】 dictionaries字典【d】 )
char endchar 字符串开始的标志位(BT文件常用的标志为 整数开头【e】 list列表【e】 dictionaries字典【e】 )
size_t *pi 如果不是空指针,则存放BT文件中描述字符串的长度数据
return 相对于b位置字符串起始位置(+1是因为Bt文件规定,字符串数据格式是【字符串长度整数+:+字符串】 )
(eg ...4:weno... 如果b的起始位置是4,则返回w相对于4的相对位置,也就是返回值是2)
*/
size_t buf_long(const char *b,size_t len,char beginchar,char endchar,int64_t *pi)
{
const char *p = b;
const char *psave;
if(2 > len) return 0; /* buffer too small */
//跳过标志位【l,d,i三种】,文件指针【p】移动到标志位的下一位
if( beginchar ){
if(*p != beginchar) return 0;
p++; len--;
}
// isdigit函数判断当前指针位置的数据是否为十进制整数类型,是整数类型,p指针地址加一,文件剩余长度len减一
for(psave = p; len && isdigit(*p); p++,len--) ;
if(!len || MAX_INT_SIZ < (p - psave) || *p != endchar) return 0;
/*
传入的pi指针不为空
存在beginchar,则文件指针地址加以,通过strtoll函数获取对应地址的十进制整数
不存在beginchar,通过strtoll函数获取对应地址的十进制整数
*/
if( pi ){
if( beginchar ) *pi = strtoll(b + 1,(char**) 0,10);
else *pi=strtoll(b,(char**) 0,10);
}
//返回int64_t *pi中数字描述的字符串,相对于b位置(+1是因为Bt文件规定,字符串数据格式是【字符串长度整数+:+字符串】 eg 4:weno)
return (size_t)( p - b + 1 );
}
/*
const char *b const类型BT文件指针
size_t len BT文件长度
char beginchar 字符串开始的标志位(BT文件常用的标志为 整数开头【i】 list列表【l】 dictionaries字典【d】 )
char endchar 字符串开始的标志位(BT文件常用的标志为 整数开头【e】 list列表【e】 dictionaries字典【e】 )
size_t *pi 如果不是空指针,则存放BT文件中描述字符串的长度数据 或者 是BT文件中整数对象
return 字符串相对于b的位置(相对位置)
*/
size_t buf_int(const char *b,size_t len,char beginchar,char endchar,size_t *pi)
{
size_t r;
if( pi ){
int64_t pl;
//r 字符串相对于b的位置(相对位置)
r = buf_long(b,len,beginchar,endchar,&pl);
//*pi 返回字符串有效长度
*pi = (size_t) pl;
}else{
//r 字符串相对于b的位置(相对位置)
r = buf_long(b,len,beginchar,endchar,(int64_t*) 0);
}
return r;
}
/*
const char *b const类型BT文件指针
size_t len BT文件长度
const char **pstr 二级指针,存放获取到字符串有效首地址
size_t* slen **pstr中字符串长度
buf_str函数 查找BT文件中字符串数据,文件指针b的位置一定是有效整数第一位开始
(eg ...3:dsd2:ds... 调用函数,文件指针b的地址指向3,**pstr指向dsd,slen=3,返回值是【2】的相对地址)
*/
size_t buf_str(const char *b,size_t len,const char **pstr,size_t* slen)
{
size_t rl,sl;
rl = buf_int(b,len,0,':',&sl);
if( !rl ) return 0;
if(len < rl + sl) return 0;
if(pstr) *pstr = b + rl;
if(slen) *slen = sl;
// 返回所查找的【也就是*pstr中】字符串最后一位有效位加一的相对地址(也就是下一次查找的相对位置)
return( rl + sl );
}
// 返回整数类型最后一位有效位+1的地址(相对位置)(eg ....i123e4:legn... 调用函数返回【e】后数字【4】的地址)
size_t decode_int(const char *b,size_t len)
{
return(buf_long(b,len,'i','e',(int64_t*) 0));
}
// 返回数字后字符串最后一位有效地址+1的地址(相对位置) (eg ....4:legn2:qw... 调用函数返回【n】后数字【2】的地址)
size_t decode_str(const char *b,size_t len)
{
return (buf_str(b,len,(const char**) 0,(size_t*) 0));
}
/*
const char *b const类型BT文件指针
size_t len BT文件长度
const char *keylist BT文件中的字典数据中key值
decode_dict函数 1.返回字典数据最后一位有效地址+1的地址(相对位置) (eg ....d4:legn2:qwe1:c... 调用函数返回【e】后数字【1】的地址)
2.返回等于const char *keylist中数据的最后一位有效位+1地址(相对位置)(eg ....d4:legn2:qwe1:c... const char *keylist=legn 则调用函数返回【n】后数字【2】的地址)
*/
size_t decode_dict(const char *b,size_t len,const char *keylist)
{
size_t rl,dl,nl;
const char *pkey;
// 相对于const char *b的位置的相对偏移量
dl = 0;
if(2 > len || *b != 'd') return 0;
// dl++ 字典数据总是以d开头,dl++跳过d标志位,以d的下一位开始遍历数据
dl++; len--;
// 字典数据以e为结尾标志
for(;len && *(b + dl) != 'e';){
rl = buf_str(b + dl,len,&pkey,&nl);
if( !rl || KEYNAME_SIZ < nl) return 0;
dl += rl;
len -= rl;
if(keylist && compare_key(pkey,nl,keylist) == 0){
pkey = next_key(keylist);
if(! *pkey ) return dl;
rl = decode_dict(b + dl,len, pkey);
if( !rl ) return 0;
return dl + rl;
}
rl = decode_rev(b + dl,len,(const char*) 0);
if( !rl ) return 0;
dl += rl;len -= rl;
}
if( !len || keylist) return 0;
return dl + 1; /* add the last char 'e' */
}
/*
decode_list函数 1.返回list列表数据最后一位有效地址+1的地址(相对位置) (eg ....ld4:legn2:qwee1:c... 调用函数返回第二个【e】后数字【1】的地址)
2.返回等于const char *keylist中数据的最后一位有效位+1地址(相对位置)(eg ....ld4:legn2:qwee1:c... const char *keylist=legn 则调用函数返回【n】后数字【2】的地址)
*/
size_t decode_list(const char *b,size_t len,const char *keylist)
{
size_t ll,rl;
ll = 0;
if(2 > len || *b != 'l') return 0;
len--; ll++;
for(;len && *(b + ll) != 'e';){
rl = decode_rev(b + ll,len,keylist);
if( !rl ) return 0;
ll += rl; len -= rl;
}
if( !len ) return 0;
return ll + 1; /* add last char 'e' */
}
/*
decode_int函数 返回整数类型最后一位有效位+1的地址(eg ....i123e4:legn... 调用函数返回【e】后数字【4】的地址)
decode_dict函数 1.返回字典数据最后一位有效地址+1的地址 (eg ....d4:legn2:qwe1:c... 调用函数返回【e】后数字【1】的地址)
2.返回等于const char *keylist中数据的最后一位有效位+1地址(eg ....d4:legn2:qwe1:c... const char *keylist=legn 则调用函数返回【n】后数字【2】的地址)
decode_list函数 1.返回list列表数据最后一位有效地址+1的地址 (eg ....ld4:legn2:qwee1:c... 调用函数返回第二个【e】后数字【1】的地址)
2.返回等于const char *keylist中数据的最后一位有效位+1地址(eg ....ld4:legn2:qwee1:c... const char *keylist=legn 则调用函数返回【n】后数字【2】的地址)
decode_str函数 返回数字后字符串最后一位有效地址+1的地址 (eg ....4:legn2:qw... 调用函数返回【n】后数字【2】的地址)
decode_rev函数 以文件指针b的位置为有效数据起点(根据BT文件格式,这个起点只有【i】,【d】,【l】和【数字】四种可能),根据keylist的有或空来返回下一个数据起点(相对位置)
*/
size_t decode_rev(const char *b,size_t len,const char *keylist)
{
if( !b ) return 0;
switch( *b ){
case 'i': return decode_int(b,len);
case 'd': return decode_dict(b,len,keylist);
case 'l': return decode_list(b,len,keylist);
default: return decode_str(b,len);
}
}
/*
const char *b const类型BT文件指针
size_t len BT文件长度
const char *keylist BT文件中查找对象
const char **ps 二级指针,存放根据keylist获取的数据
size_t *pi,int64_t *pl **ps字符串的长度
int method QUERY_STR 获取keylist对应的字符串数据
QUERY_INT 获取keylist对应的整数类型为size_t(eg ...lengthi1328e... 获取length对应整数1328,BT格式中【i。。。e】之间是描述存放整数)
QUERY_POS 获取keylist对应数据的起始位置(相对位置),如果*pi传入的参数不为空,同时将对应数据的有效长度存放到*pi中
QUERY_LONG 如果keylist对应的数据类型为int64_t
*/
size_t decode_query(const char *b,size_t len,const char *keylist,const char **ps,size_t *pi,int64_t *pl,int method)
{
size_t pos;
char kl[KEYNAME_LISTSIZ];
strcpy(kl,keylist);
pos = decode_rev(b, len, kl);
if( !pos ) return 0;
switch(method){
//获取BT文件中字符串和该字符串长度,分别写入到ps和pi中,返回下一个有效起点相对于b + pos的相对位置
case QUERY_STR: return(buf_str(b + pos,len - pos, ps, pi));
//获取BT文件中整数写入pi中,返回下一个有效起点相对于b + pos的相对位置
case QUERY_INT: return(buf_int(b + pos,len - pos, 'i', 'e', pi));
//返回查找数据的相对位置,如果pi不为空则存放查找数据的长度(字符串位置标志是从零开始)
case QUERY_POS:
if(pi) *pi = decode_rev(b + pos, len - pos, (const char*) 0);
return pos;
//获取BT文件中整数写入pl中,返回下一个有效起点相对于b + pos的相对位置
case QUERY_LONG: return(buf_long(b + pos,len - pos, 'i', 'e', pl));
default: return 0;
}
}
size_t bencode_buf(const char *buf,size_t len,FILE *fp)
{
char slen[MAX_INT_SIZ];
if( MAX_INT_SIZ <= snprintf(slen, MAX_INT_SIZ, "%d:", (int)len) ) return 0;
if( fwrite( slen, strlen(slen), 1, fp) != 1) return 0;
if( fwrite(buf, len, 1, fp) != 1 ) return 0;
return 1;
}
size_t bencode_str(const char *str, FILE *fp)
{
return bencode_buf(str, strlen(str), fp);
}
size_t bencode_int(const uint64_t integer, FILE *fp)
{
char buf[MAX_INT_SIZ];
if( EOF == fputc('i', fp)) return 0;
if( MAX_INT_SIZ <=
snprintf(buf, MAX_INT_SIZ, "%llu", (unsigned long long)integer) )
return 0;
if( fwrite(buf, strlen(buf), 1, fp) != 1 ) return 0;
return (EOF == fputc('e', fp)) ? 0: 1;
}
size_t bencode_begin_dict(FILE *fp)
{
return (EOF == fputc('d',fp)) ? 0 : 1;
}
size_t bencode_begin_list(FILE *fp)
{
return (EOF == fputc('l',fp)) ? 0 : 1;
}
size_t bencode_end_dict_list(FILE *fp)
{
return (EOF == fputc('e',fp)) ? 0 : 1;
}
size_t bencode_path2list(const char *pathname, FILE *fp)
{
const char *pn;
const char *p = pathname;
if( bencode_begin_list(fp) != 1 ) return 0;
for(; *p;){
pn = strchr(p, PATH_SP);
if( pn ){
if( bencode_buf(p, pn - p, fp) != 1) return 0;
p = pn + 1;
}else{
if( bencode_str(p, fp) != 1) return 0;
break;
}
}
return bencode_end_dict_list(fp);
}
size_t decode_list2path(const char *b, size_t n, char *pathname)
{
const char *pb = b;
const char *s = (char *) 0;
size_t r,q;
if( 'l' != *pb ) return 0;
pb++;
n--;
if( !n ) return 0;
for(; n;){
if(!(r = buf_str(pb, n, &s, &q)) ) return 0;
memcpy(pathname, s, q);
pathname += q;
pb += r; n -= r;
if( 'e' != *pb ){*pathname = PATH_SP, pathname++;} else break;
}
*pathname = '\0';
return (pb - b + 1);
}
如果对bendode.cpp文件中的函数有了一定了解,那么恭喜了,后面理解BTCONTENT.InitialFromMI函数功能就事倍功半了。
先写到这了,其中代码块【1】中还留有一个小尾巴,就是BuildFromMI和CreateFiles这个两个函数,没有做解析。
未完待续 ,,,,,