用C语言开发一个BT下载软件 (四) ------ 代码实现-1-种子文件解析模块

2023-10-27

//parse_metafile.h

#ifndef PARSE_METAFILE
#define PARSE_METAFILE

// 保存从种子文件中获取的tracker的URL
typedef struct _Announce_list {
	char    announce[128];
	struct _Announce_list  *next;
} Announce_list;

// 保存各个待下载文件的路径和长度
typedef struct _Files {
	char    path[256];
	long    length;
	struct _Files *next;
} Files; 

int read_metafile(char *metafile_name);         // 读取种子文件
int find_keyword(char *keyword,long *position); // 在种子文件中查找某个关键词
int read_announce_list();                       // 获取各个tracker服务器的地址
int add_an_announce(char* url);                 // 向tracker列表添加一个URL

int get_piece_length();       // 获取每个piece的长度,一般为256KB
int get_pieces();             // 读取各个piece的哈希值

int is_multi_files();         // 判断下载的是单个文件还是多个文件
int get_file_name();          // 获取文件名,对于多文件,获取的是目录名
int get_file_length();        // 获取待下载文件的总长度
int get_files_length_path();  // 获取文件的路径和长度,对多文件种子有效

int get_info_hash();          // 由info关键词对应的值计算info_hash               
int get_peer_id();            // 生成peer_id,每个peer都有一个20字节的peer_id

// 释放parse_metafile.c中动态分配的内存
void release_memory_in_parse_metafile();
// 调用本文件中定义的函数,完成解析种子文件
int  parse_metafile(char *metafile);

#endif


//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  *metafile_content = NULL; // 保存种子文件的内容
long  filesize;                 // 种子文件的长度

int       piece_length  = 0;    // 每个piece的长度,通常为256KB即262144字节
char      *pieces       = NULL; // 保存每个pieces的哈希值,每个哈希值为20字节
int       pieces_length = 0;    // pieces缓冲区的长度

int       multi_file    = 0;    // 指明是单文件还是多文件
char      *file_name    = NULL; // 对于单文件,存放文件名;对于多文件,存放目录名
long long file_length   = 0;    // 存放待下载文件的总长度
Files     *files_head   = NULL; // 只对多文件种子有效,存放各个文件的路径和长度

unsigned char info_hash[20];    // 保存info_hash的值,连接tracker和peer时使用
unsigned char peer_id[20];      // 保存peer_id的值,连接peer时使用

Announce_list *announce_list_head = NULL; // 用于保存所有tracker服务器的URL

// 读取种子文件
int read_metafile(char *metafile_name)
{
	long  i;
	
	// 以二进制、只读的方式打开文件
	FILE *fp = fopen(metafile_name,"rb");
	if(fp == NULL) {
		printf("%s:%d can not open file\n",__FILE__,__LINE__);
		return -1;
	}
	
	// 获取种子文件的长度
	fseek(fp,0,SEEK_END);
	filesize = ftell(fp);
	if(filesize == -1) {
		printf("%s:%d fseek failed\n",__FILE__,__LINE__);
		return -1;
	}
	
	metafile_content = (char *)malloc(filesize+1);
	if(metafile_content == NULL) {
		printf("%s:%d malloc failed\n",__FILE__,__LINE__);
		return -1;
	}
	
	// 读取种子文件的内容到metafile_content缓冲区中
	fseek(fp,0,SEEK_SET);
	for(i = 0; i < filesize; i++)
		metafile_content[i] = fgetc(fp);
	metafile_content[i] = '\0';

	fclose(fp); 

#ifdef DEBUG
	printf("metafile size is: %ld\n",filesize);
#endif	
	
	return 0;
}

// 在种子文件中查找某个关键词
int find_keyword(char *keyword,long *position)
{
	long i;

	*position = -1;
	if(keyword == NULL)  return 0;

	for(i = 0; i < filesize-strlen(keyword); i++) {
		if( memcmp(&metafile_content[i], keyword, strlen(keyword)) == 0 ) {
			*position = i;
			return 1;
		}
	}
	
	return 0;
}

// 获取各个tracker服务器的地址
int read_announce_list()
{
	Announce_list  *node = NULL;
	Announce_list  *p    = NULL;
	int            len   = 0;
	long           i;

	if( find_keyword("13:announce-list",&i) == 0 ) {	/*只有一个announce,即只有一个Tracker的URL,无备用URL*/
		if( find_keyword("8:announce",&i) == 1 ) {
			i = i + strlen("8:announce");
			while( isdigit(metafile_content[i]) ) {
				len = len * 10 + (metafile_content[i] - '0');	/*本段代码,是将作为文本的数字,转换成整数类型的数字。乘10,是进位,多一位的话,先前的数字自然要进位所以要乘10,目前为止最后的数字作为个位*/
				i++;											/*减0,意思是以字符‘0’作为基准,计算某个数和0之间的差,则该差的值,就是那个字符型数字对应的整数型数字了*/
			}
			i++;  // 跳过 ':'

			node = (Announce_list *)malloc(sizeof(Announce_list));
			strncpy(node->announce,&metafile_content[i],len);
			node->announce[len] = '\0';
			node->next = NULL;
			announce_list_head = node;
		}
	} 
	else {  // 如果有13:announce-list关键词就不用处理8:announce关键词
		i = i + strlen("13:announce-list");
		i++;         // skip 'l'
		while(metafile_content[i] != 'e') {
			i++;     // skip 'l'
			while( isdigit(metafile_content[i]) ) {
				len = len * 10 + (metafile_content[i] - '0');
				i++;
			}
			if( metafile_content[i] == ':' )  i++;
			else  return -1;

			// 只处理以http开头的tracker地址,不处理以udp开头的地址
			if( memcmp(&metafile_content[i],"http",4) == 0 ) {
				node = (Announce_list *)malloc(sizeof(Announce_list));
				strncpy(node->announce,&metafile_content[i],len);
				node->announce[len] = '\0';
				node->next = NULL;

				if(announce_list_head == NULL)
					announce_list_head = node;
				else {
					p = announce_list_head;
					while( p->next != NULL){
						p = p->next; // 使p指向最后个结点
					}
					p->next = node; // node成为tracker列表的最后一个结点
				}
			}

			i = i + len;
			len = 0;
			i++;    // skip 'e'
			if(i >= filesize)  return -1;
		}	
	}

#ifdef DEBUG
	p = announce_list_head;
	while(p != NULL) {
		printf("%s\n",p->announce);
		p = p->next;
	}
#endif	
	
	return 0;
}

// 连接某些tracker时会返回一个重定向URL,需要连接该URL才能获取peer
int add_an_announce(char *url)
{
	Announce_list *p = announce_list_head, *q;

	// 若参数指定的URL在tracker列表中已存在,则无需添加
	while(p != NULL) {
		if(strcmp(p->announce,url) == 0){
			break;
		}
		p = p->next;
	}

	if(p != NULL)
		return 0;

	q = (Announce_list *)malloc(sizeof(Announce_list));
	strcpy(q->announce,url);
	q->next = NULL;
	
	p = announce_list_head;
	if(p == NULL){
		announce_list_head = q;
		return 1; 
	}
	while(p->next != NULL){
		p = p->next;
	}
	p->next = q;
	return 1;
}

// 判断下载的是单个文件还是多个文件
int is_multi_files()
{
	long i;

	if( find_keyword("5:files",&i) == 1 ) {
		multi_file = 1;
		return 1;
	}

#ifdef DEBUG
	// printf("is_multi_files:%d\n",multi_file);
#endif

	return 0;
}

// 获取每个piece的长度,一般为256KB
int get_piece_length()
{
	long i;

	if( find_keyword("12:piece length",&i) == 1 ) {
		i = i + strlen("12:piece length");  // skip "12:piece length"
		i++;  // skip 'i'
		while(metafile_content[i] != 'e') {
			piece_length = piece_length * 10 + (metafile_content[i] - '0');
			i++;
		}
	} else {
		return -1;
	}

#ifdef DEBUG
	printf("piece length:%d\n",piece_length);
#endif

	return 0;
}

// 读取各个piece的哈希值
int get_pieces()
{
	long i;

	if( find_keyword("6:pieces", &i) == 1 ) {
		i = i + 8;     // skip "6:pieces"
		while(metafile_content[i] != ':') {
			pieces_length = pieces_length * 10 + (metafile_content[i] - '0');
			i++;
		}
		i++;           // skip ':'
		pieces = (char *)malloc(pieces_length+1);
		memcpy(pieces,&metafile_content[i],pieces_length);
		pieces[pieces_length] = '\0';
	} else {
		return -1;
	}

#ifdef DEBUG
	printf("get_pieces ok\n");
#endif

	return 0;
}

// 获取文件名,对于多文件,获取的是目录名
int get_file_name()
{
	long  i;
	int   count = 0;

	if( find_keyword("4:name", &i) == 1 ) {
		i = i + 6;  // skip "4:name"
		while(metafile_content[i] != ':') {
			count = count * 10 + (metafile_content[i] - '0');
			i++;
		}
		i++;        // skip ':' 
		file_name = (char *)malloc(count+1);
		memcpy(file_name,&metafile_content[i],count);
		file_name[count] = '\0';
	} else {
		return -1;
	}

#ifdef DEBUG
	// 由于可能含有中文字符,因此可能打印出乱码
	// printf("file_name:%s\n",file_name);
#endif

	return 0;
}

// 获取待下载文件的总长度
int get_file_length()
{
	long i;

	if(is_multi_files() == 1)  {
		if(files_head == NULL)
			get_files_length_path();
		Files *p = files_head;
		while(p != NULL) {
			file_length += p->length; 
			p = p->next; 
		}
	} else {
		if( find_keyword("6:length",&i) == 1 ) {
			i = i + 8;  // skip "6:length"
			i++;        // skip 'i' 
			while(metafile_content[i] != 'e') {
				file_length = file_length * 10 + (metafile_content[i] - '0');
				i++;
			}	
		}
	}
	
#ifdef DEBUG
	printf("file_length:%lld\n",file_length);
#endif

	return 0;
}

// 获取文件的路径和长度,对多文件种子有效
int get_files_length_path()
{
	long   i;
	int    length;
	int    count;
	Files  *node  = NULL;
	Files  *p     = NULL;

	if(is_multi_files() != 1) {
		return 0;
	}
	
	for(i = 0; i < filesize-8; i++) {
		if( memcmp(&metafile_content[i],"6:length",8) == 0 )
		{
			i = i + 8;  // skip "6:length"
			i++;        // skip 'i' 
			length = 0;
			while(metafile_content[i] != 'e') {
				length = length * 10 + (metafile_content[i] - '0');
				i++;
			}
			node = (Files *)malloc(sizeof(Files));
			node->length = length;
			node->next = NULL;
			if(files_head == NULL)
				files_head = node;
			else {
				p = files_head;
				while(p->next != NULL) p = p->next;
				p->next = node;
			}
		}
		if( memcmp(&metafile_content[i],"4:path",6) == 0 )
		{
			i = i + 6;  // skip "4:path"
			i++;        // skip 'l'
			count = 0;
			while(metafile_content[i] != ':') {
				count = count * 10 + (metafile_content[i] - '0');
				i++;
			}
			i++;        // skip ':'
			p = files_head;
			while(p->next != NULL) p = p->next;
			memcpy(p->path,&metafile_content[i],count);
			*(p->path + count) = '\0';
		}
	}

#ifdef DEBUG
	// 由于可能含有中文字符,因此可能打印出乱码
	// p = files_head;
	// while(p != NULL) {
	//	 printf("%ld:%s\n",p->length,p->path);
	//	 p = p->next;
	// }
#endif

	return 0;
}

// 由info关键词对应的值计算info_hash
/*
由关键字“4:info”对应的值来计算info_hash,该关键字对应的是一个B编码的字典,问题的关键在找到与'd'对应的‘e’
思路是:每当遇到字典和列表起始符'd',则将push_pop的值加1
	遇到整数的起始符‘i’则一直扫描直到找到与之对应的终结符'e'
	遇到一个0~9的数字说明接下来是字符串,跳过该字符串继续扫描
	遇到‘e’则将push_pop减1,减1后,push_pop值为0,说明已经找到了与‘d’匹配的‘e’
*/
int get_info_hash()
{
	int   push_pop = 0;
	long  i, begin, end;

	if(metafile_content == NULL)
		return -1;

	if( find_keyword("4:info",&i) == 1 ) {
		begin = i+6;  // begin是关键字"4:info"对应值的起始下标
	} else {
		return -1;
	}

	i = i + 6;        // skip "4:info"
	for(; i < filesize; )
		if(metafile_content[i] == 'd') { 
			push_pop++;
			i++;
		} else if(metafile_content[i] == 'l') {
			push_pop++;
			i++;
		} else if(metafile_content[i] == 'i') {
			i++;  // skip i
			if(i == filesize)  return -1;
			while(metafile_content[i] != 'e') {
				if((i+1) == filesize)  return -1;
				else i++;
			}
			i++;  // skip e
		} else if((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {
			int number = 0;
			while((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {
				number = number * 10 + metafile_content[i] - '0';
				i++;
			}
			i++;  // skip :
			i = i + number;
		} else if(metafile_content[i] == 'e') {
			push_pop--;
			if(push_pop == 0) { end = i; break; }
			else  i++; 
		} else {
			return -1;
		}
	if(i == filesize)  return -1;

	SHA1_CTX context;
	SHA1Init(&context);
	SHA1Update(&context, &metafile_content[begin], end-begin+1);
	SHA1Final(info_hash, &context);

#ifdef DEBUG
	printf("info_hash:");
	for(i = 0; i < 20; i++)  
		printf("%.2x ",info_hash[i]);
	printf("\n");
#endif

	return 0;
}

// 生成peer_id,每个peer都有一个20字节的peer_id
int get_peer_id()
{
	// 设置产生随机数的种子
	srand(time(NULL));
	// 生成随机数,并把其中12位赋给peer_id,peer_id前8位固定为-TT1000-
	sprintf(peer_id,"-TT1000-%12d",rand());

#ifdef DEBUG
	int i;
	printf("peer_id:");
	for(i = 0; i < 20; i++)  printf("%c",peer_id[i]);
	printf("\n");
#endif

	return 0;
}

// 释放parse_metafile.c中动态分配的内存
void release_memory_in_parse_metafile()
{
	Announce_list *p;
	Files         *q;
	
	if(metafile_content != NULL)
		free(metafile_content);
	if(file_name != NULL)
		free(file_name);
	if(pieces != NULL)
		free(pieces);
	
	while(announce_list_head != NULL) {
		p = announce_list_head;
		announce_list_head = announce_list_head->next;
		free(p);
	}

	while(files_head != NULL) {
		q = files_head;
		files_head = files_head->next;
		free(q);
	}
}

// 调用本文件中定义的函数,完成解析种子文件
int parse_metafile(char *metafile)
{
	int ret;

	// 读取种子文件
	ret = read_metafile(metafile);
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	
	// 从种子文件中获取tracker服务器的地址
	ret = read_announce_list();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

	// 判断是否为多文件
	ret = is_multi_files();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	
	// 获取每个piece的长度,一般为256KB
	ret = get_piece_length();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	
	// 读取各个piece的哈希值
	ret = get_pieces();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	
	// 获取要下载的文件名,对于多文件的种子,获取的是目录名
	ret = get_file_name();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

	// 对于多文件的种子,获取各个待下载的文件路径和文件长度
	ret = get_files_length_path();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	
	// 获取待下载的文件的总长度
	ret = get_file_length();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

	// 获得info_hash,生成peer_id
	ret = get_info_hash();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }
	ret = get_peer_id();
	if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用C语言开发一个BT下载软件 (四) ------ 代码实现-1-种子文件解析模块 的相关文章

随机推荐

  • STM8S105K4T6硬件IIC调试小结

    1 IIC初始化 具体时钟设置参考此篇文章 https blog csdn net u014397533 article details 46495905 void I2C Init void I2C CR1 0x00 禁止I2C外设 此句
  • 日本语语料库

    来自 日语语料库建设的现状综述 上海外国语大学 毛文伟 2009年 1 EDR语料库 EDR 该语料库由日本电子化辞书研究所开发 并于1995年推出 素材选自新闻报道和杂志 规模为 20 万句 另有 10 万 句左右的英语语料 在原始语料的
  • 深度学习图像融合 合成 协调笔记

    目录 图像合成最新资料汇总1 图像合成最新资料汇总2 图像渲染 pip install poetry
  • 【Espruino】NO.05 按键是你的仆人

    http blog csdn net qwert1213131 article details 27104341 本文属于个人理解 能力有限 纰漏在所难免 还望指正 小鱼有点电 按键 生活中随处可见 手机 电脑 家用电器 用来执行各种功能
  • Linux中用stat命令查看文件时3个时间点解析

    有些时候 我们需要使用stat命令来查看文件的详细信息 另外联想下 ls l命令显示的是什么时间 touch命令修改文件的时间戳 修改的又是什么时间 在这里我们一起来试验下 首先 我们来看下stat情况 如图所示 会出现3个类型的时间 分别
  • CPU时间与系统时间(CPU time and wall clock time)

    CPU时间是指一段程序在CPU上面运行消耗的时间 也是内核时间 kernel time 在Linux Unix系统里面 C 程序的COU时间可以用一些第三方的库提供的函数测出 但是在Windows系统里面 没有可以直接使用的第三方函数 在这
  • Session和Cookie实现购物车

    来自森大科技官方博客 http www cnsendblog com index php p 342 GPS平台 网站建设 软件开发 系统运维 找森大网络科技 http cnsendnet taobao com 使用Session和Cook
  • 自定义Mybatis框架

    目录 自定义Mybatis分析 轮子缺少的配件 组装轮子 制定骨架 解析配置文件 类关系梳理 创建默认实现类 实现基于注解的查询 目录结构 流程图 通过快速入门示例 Mybatis快速入门 我们发现使用 mybatis 是非常容易的一件事情
  • easyui dialog 子窗口jsp(被弹出窗口)调用父jsp页面方法操作父jsp

    父jsp monthDuty jsp 选中tab2 var selectTabByIndex function tabId tabs select 1 中间js文件 monthDutyJs js var dialog parent sunn
  • 「Linux-基础」CentOS 8 LVM逻辑卷管理

    LVM逻辑卷管理 枫梓林 提示 建议按着步骤来 文章目录 LVM逻辑卷管理 1 简介 2 建立LVM的步骤 3 逻辑卷管理及部署 1 磁盘分区 2 物理卷管理 建立物理卷 扫描物理卷 显示物理卷 删除物理卷 3 卷组管理 建立卷组 扫描卷组
  • STM32(HAL库)通过ADC读取MQ2数据

    目录 1 简介 2 CubeMX初始化配置 2 1 基础配置 2 1 1 SYS配置 2 1 2 RCC配置 2 2 ADC外设配置 2 3 串口外设配置 2 4 项目生成 3 KEIL端程序整合 3 1 串口重映射 3 2 ADC数据采集
  • 实验5-8 使用函数求圆台体积 (10 分)

    实验5 8 使用函数求圆台体积 10 分 本题要求实现函数求圆台体积 定义并调用函数volume tc r lower r upper h 计算下底半径为r lower 上底半径为r upper 高度为h的圆台的体积 函数类型是double
  • 卷积学习与传统稀疏编码、ICA模型学习区别(逐步补充)

    逐步总结 有待补充 无监督学习知识框架 这种分类不合适 稀疏编码等也可以从统计学角度看做模型学习与参数选择 实际上 稀疏编码是从1维信号发展起来的表示方法 近年来 稀疏编码逐渐引入信号的先验信息 由非模型向基于模型的转变 学习特色字典 单层
  • iOS开发Swift-12-列表UI,TableViewController,动态响应Button勾选-待办事项App(1)

    1 创建新项目 为项目添加图标 2 将Table View Controller添加到界面中 将箭头移动到Table View上来 代表它是首页 根页面 选中ViewController 点击Delete 对它进行删除 将代码ViewCon
  • 网络/网络编程

    网络 网络编程部份 1 connect方法会阻塞 请问有什么方法可以避免其长时间阻塞 答 最通常的方法最有效的是加定时器 也可以采用非阻塞模式 2 网络中 如果客户端突然掉线或者重启 服务器端怎么样才能立刻知道 答 若客户端掉线或者重新启动
  • 服务器文件直接复制到本地,本地文件直接复制到云服务器

    本地文件直接复制到云服务器 内容精选 换一换 在本地主机和Windows弹性云服务器上分别安装QQ exe等工具进行数据传输 使用远程桌面连接mstsc方式进行数据传输 该方式不支持断点续传 可能存在传输中断的情况 因此不建议上传大文件 文
  • MySQL 5.6解压缩版安装配置方法图文教程(win10)

    MySQL 5 6解压缩版安装配置方法图文教程 win10 1 首先 我们需要得到这个安装包 解压下载到本地 可在小编网盘中找到 我已共享链接 可直接下载 比如 现在我们把他解压到我们本地的D MySQL文件夹下 现在我们就可以看到 当前目
  • 【WiFi】Wi-Fi 6(802.11ax)解析24:802.11ax中MU-MIMO和OFDMA的区别

    目录 1 序言 2 OFDMA 2 MU MIMO 3 MAC层部分 MU MIMO和OFDMA 4 结语 5 参考 1 序言 笔者将自己对于802 11ax中的MU MIMO和OFDMA的区别做了一个简单的总结 因为很多非通信技术专业的童
  • 临界区对象TCriticalSection(Delphi) 与 TRtlCriticalSection 的区别

    TRtlCriticalSection 是一个结构体 在windows单元中定义 TCriticalSection是在SyncObjs单元中实现的类 它对上面的那些临界区操作API函数进行了了封装 简化并方便了在Delphi的使用 如TCr
  • 用C语言开发一个BT下载软件 (四) ------ 代码实现-1-种子文件解析模块

    parse metafile h ifndef PARSE METAFILE define PARSE METAFILE 保存从种子文件中获取的tracker的URL typedef struct Announce list char an