Ctorrent源码解析(三)

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

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

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

          书接上一章,我们再来看一下GetHashValue函数都做了什么事情,源码如下:

// idx 当前需要制作sha1值的piece片段,从零piece开始
// md  idx的piece制作sha1值存储位置 
int btContent::GetHashValue(size_t idx,unsigned char *md)
{
   
  if( global_buffer_size < m_piece_length ){
    delete []global_piece_buffer;
    global_piece_buffer = new char[m_piece_length];
    global_buffer_size = global_piece_buffer ? m_piece_length : 0;
  }
// 函数详解见下面  
  if( ReadPiece(global_piece_buffer,idx) < 0 ) return -1;
// 计算sha1值写入md指定位置中  
  Sha1(global_piece_buffer,GetPieceLength(idx),md);
  return 0;
}

     ReadPiece函数主要调用GetPieceLengthReadSlice这两个函数完成piece数据的读取。我们先看GetPieceLength函数

//返回最后一个piece的实际长度(制作种子的文件不一定可以正好被piece_length所整除)
size_t btContent::GetPieceLength(size_t idx)
{
  //因为你idx是从零开始计数,所以当idx==整个piece数(也就是m_npieces)-1时,就证明已经是本次的最 
    后一个piece
  return (idx == m_npieces - 1 &&
          idx == m_btfiles.GetTotalLength() / m_piece_length) ?
    (size_t)(m_btfiles.GetTotalLength() % m_piece_length) :
    m_piece_length;
}

接下来我们再看核心函数ReadSlice

//buf 存放读取的文件信息
//idx 读取第几个piece,用来后面计算偏移量
//off 偏移量参数
//len 实际读取数据长度
ssize_t btContent::ReadSlice(char *buf,size_t idx,size_t off,size_t len)
{
  ssize_t retval = 0;
  uint64_t offset = (uint64_t)idx * (uint64_t)m_piece_length + off;
  // m_cache_size==0 表示需持久化中读取文件数据
  // m_btfiles.IO核心读取文件函数,详解见下文
  if( !m_cache_size ) return buf ? m_btfiles.IO(buf, offset, len, 0) : 0;
.
.
.
.


}

接下来我们再看核心文件读取函数btFiles::IO

ssize_t btFiles::IO(char *buf, uint64_t off, size_t len, const int iotype)
{
  uint64_t n = 0;
  off_t pos,nio;
  BTFILE *pbf = m_btfhead;
  //计算读取数据的偏移量加上本次读取数据的长度是否大于整个文件(文件集)的长度
  if( (off + (uint64_t)len) > m_total_files_length ){
    CONSOLE.Warning(1, "error, data offset %llu length %lu out of range",
      (unsigned long long)off, (unsigned long)len);
    return -1;
  }
// 计算文件集中长度刚好大于本次读取的偏移量,定位本次读取数据的文件位置,记录长度为n,
  for(; pbf; pbf = pbf->bf_next){
    n += (uint64_t) pbf->bf_length;
    if(n > off) break;
  }

  if( !pbf ){
    CONSOLE.Warning(1, "error, failed to find file for offset %llu",
      (unsigned long long)off);
    return -1;
  }

  // n - pbf->bf_lengt 定位文件的起始地址
  // pos 本次读取数据的具体位置
  pos = off - (n - pbf->bf_length);
        
  // 存在跨文件读取数据,所以用len不为零为循环条件读取数据
  for(; len ;){
    // 打开pbf中当前指针指定文件
    if( (!pbf->bf_flag_opened || (iotype && pbf->bf_flag_readonly)) &&
        _btf_open(pbf, iotype) < 0 ){
      CONSOLE.Warning(1, "error, failed to open file \"%s\":  %s",
        pbf->bf_filename, strerror(errno));
      return -1;
    }

    pbf->bf_last_timestamp = now;

#ifdef HAVE_FSEEKO
    if( fseeko(pbf->bf_fp,pos,SEEK_SET) < 0 ){
#else
    // fseek系统调用,定位文件指针到pos处
    if( fseek(pbf->bf_fp,(long) pos,SEEK_SET) < 0 ){
#endif
      CONSOLE.Warning(1, "error, failed to seek to %llu on file \"%s\":  %s",
        (unsigned long long)pos, pbf->bf_filename, strerror(errno));
      return -1;
    }
//  }
    // nio 如果len小于这个文件剩余读取长度,本次直接读取len大小数据,否则读取该文件剩余大小
    nio = (len < pbf->bf_length - pos) ? len : (pbf->bf_length - pos);
    // iotype为0,表示读取文件数据  
    if(0 == iotype){
      errno = 0;
    // fread读取pos位置长度为nio的数据到buf中      
      if( 1 != fread(buf,nio,1,pbf->bf_fp) && ferror(pbf->bf_fp) ){
        CONSOLE.Warning(1, "error, read failed at %llu on file \"%s\":  %s",
          (unsigned long long)pos, pbf->bf_filename, strerror(errno));
        return -1;
      }
    }else{
      errno = 0;
      if( 1 != fwrite(buf,nio,1,pbf->bf_fp) ){
        CONSOLE.Warning(1, "error, write failed at %llu on file \"%s\":  %s",
          (unsigned long long)pos, pbf->bf_filename, strerror(errno));
        return -1;
      }
      if( fflush(pbf->bf_fp) == EOF ){
        CONSOLE.Warning(1, "error, flush failed at %llu on file \"%s\":  %s",
          (unsigned long long)pos, pbf->bf_filename, strerror(errno));
        return -1;
      }
    }
    // len-noi 用来计算本次数据是否全部读取
    len -= nio;
    //  如果len不为0,移动buf指针到下一次写入文件数据的起始位置
    buf += nio;
    // 如果len不是0,本次没有完全读取到符合len大小的数据,要回到循环开始处,继续从下一个文件读 
       取剩余长度数据
    // 换言之,就是要跨文件读取数据才会存在len-nio不为零
    if( len ){
      do{
        pbf = pbf->bf_next;
        if( !pbf ){
          CONSOLE.Warning(1,
            "error, data left over with no more files to write");
          return -1;
        }
      }while( 0==pbf->bf_length );
      pos = 0;
    }
  } // end for
  return 0;
}

     最后总结一下GetHashValue函数的主要作用,循环调用该函数按照指定的长度读取文件集中的数据,然后交给sha1函数生成对应的散列值,最后生成对应的BT种子文件中信息。

     到此,Ctorrent生成种子文件的大致过程已经有了初步的了解,接下来几篇文章主要关于Ctorrent如何下载文件进行分析。

未完待续,,,,