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函数主要调用GetPieceLength和ReadSlice这两个函数完成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如何下载文件进行分析。
未完待续,,,,