Ctorrent源码解析(四)

Ctorrent源码版本【ctorrent-dnh3.3.2】

URL:【http://www.rahul.net/dholmes/ctorrent/ctorrent-dnh3.3.2.tar.gz】

目的:工作接触BT软件的二次开发,顺便记录一下开源工具Ctorrent的原码的阅读过程
            

       书接上一章,前面介绍了Ctorrent制作种子文件的具体步骤,已经对部分重点函数做了详细解释,接下来,将继续介绍Ctorrent是如何根据种子进行下载任务。

       Ctorrent按序通过BTCONTENT.InitialFromMIWORLD.Initial_ListenPortTracker.InitialDownloader这四个函数进行下载任务。

     先看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】中还留有一个小尾巴,就是BuildFromMICreateFiles这个两个函数,没有做解析。

未完待续 ,,,,,