linux下c/c++实例之十四c实现的bt软件下载(记录)
一、简介
可能许多人使用过比特彗星(BitComet)、比特精灵(BitSpirit)、迅雷下载过自己喜欢的影片、电视剧、网络游戏;还有很多人使用过PPLive、PPStream、沸点、QQ直播等免费的网络电视直播软件在线观看自己喜欢的影片。所有这些软件都采用了一种近年来流行起来的协议,BitTorrent协议,简称BT协议。
2003年,年轻的软件工程师Bram Cohen发明了BitTorrent协议。在短短的时间内,BT协议的通信流量占据了互联网总流量的六成以上。BT协议成为一种新的变革技术,因此也催生了很多BT软件,如BitComet、BitSpirit、Azureus,PPLive、PPStream。
如下是使用C语言在Linux环境下开发了一个BT软件,并介绍BT协议和技术,编译运行在centos6.6(linux)下。
二、详解
1、BitTorrent协议
(1)BitTorrent(简称BT)是一个文件分发协议,每个下载者在下载的同时不断向其他下载者上传已下载的数据。而在FTP、HTTP协议中,每个下载者从FTP或HTTP服务器处下载自己所需要的文件,各个下载者之间没有交互。当非常多的用户同时访问和下载服务器上的文件时,由于FTP服务器的处理能力和带宽的限制,下载速度会急剧下降,有的用户根本访问不了服务器。BT协议与FTP协议不同,它的特点是下载的人越多下载的速度越快,其原因在于每个下载者将已下载的数据提供给其他下载者下载,它充分利用了用户的上载带宽。BT协议通过一定的策略保证上传的速度越快,下载的速度也越快。
(2)基于BT协议的文件分发系统的构成
基于BT协议的文件分发系统由以下几个实体构成。
(1)一个Web服务器。
(2)一个种子文件。
(3)一个Tracker服务器。
(4)一个原始文件提供者。
(5)一个网络浏览器。
(6)一个或多个下载者。
Web服务器上保存着种子文件,下载者使用网络浏览器(如IE浏览器)从Web服务器上下载种子文件。种子文件,又称为元原文件或metafile,它保存了共享文件的一些信息,如共享文件的文件名、文件大小、Tracker服务器的地址。种子文件通常很小,一般大小为1GB的共享文件,其种子文件不足100KB,种子文件以.torrent为后缀。Tracker服务器保存着当前下载某共享文件的所有下载者的IP和端口。原始文件提供者提供完整的共享文件供其他下载者下载,它也被称为种子,种子文件就是提供者使用BT客户端生成的。每个下载者通过运行BT客户端软件下载共享文件。我们把某个下载者本身称为客户端,把其他下载者称为peer。
BT客户端下载一个共享文件的过程是:客户端首先解析种子文件,获取待下载的共享文件的一些信息,其中包括Tracker服务器的地址。然后客户端连接Tracker获取当前下载该文件的所有下载者的IP和端口。之后客户端根据IP和端口连接其他下载者,从它们那里下载文件,同时把自己已下载的部分提供给其他下载者下载。
共享文件在逻辑上被划分为大小相同的块,称为piece,每个piece的大小通常为256KB。对于共享文件,文件的第1字节到第256K(即262144)字节为第一个piece,第256K+1字节到第512K字节为第二个piece,依此类推。种子文件中包含有每个piece的hash值。BT协议规定使用Sha1算法对每个piece生成20字节的hash值,作为每个piece的指纹。每当客户端下载完一个piece时,即对该peice使用Sha1算法计算其hash值,并与种子文件中保存的该peice的hash值进行比较,如果一致即表明下载了一个完整而正确的piece。一旦某个piece被下载,该piece即提供给其他peer下载。在实际上传和下载中,每个piece又被划分为大小相同的slice,每个slice的大小固定为16KB(16384字节)。peer之间每次传输以slice为单位。
从以上描述可以得知,待开发的BT软件(即BT客户端)主要包含以下几个功能:解析种子文件获取待下载的文件的一些信息,连接Tracker获取peer的IP和端口,连接peer进行数据上传和下载、对要发布的提供共享文件制作和生成种子文件。种子文件和Tracker的返回信息都以一种简单而高效的编码方式进行编码,称为B编码。客户端与Tracker交换信息基于HTTP协议,Tracker本身作为一个Web服务器存在。客户端与其他peer采用面向连接的可靠传输协议TCP进行通信。下面将进一步作详细的介绍。
(3)B编码
种子文件和Tracker的返回信息都是经过B编码的。要解析和处理种子文件以及Tracker的返回信息,首先要熟悉B编码的规则。B编码中有4种类型:字符串、整型、列表、字典。
字符串的编码格式为:<字符串的长度>:<字符串>,其中<>括号中的内容为必需。例如,有一个字符串spam,则经过B编码后为4:spam。
整型的编码格式为:i<十进制的整型数>e,即B编码中的整数以i作为起始符,以e作为终结符,i为integer的第一个字母,e为end的第一个字母。例如,整数3,经过B编码后为i3e,整数−3的B编码为i−3e,整数0的B编码为i0e。注意i03e不是合法的B编码,因为03不是十进制整数,而是八进制整数。
列表的编码格式为:l<任何合法的类型>e,列表以l为起始符,以e为终结符,中间可以为任何合法的经过B编码的类型,l为list的第一个字母。例如,列表l4:spam4:eggse表示两个字符串,一个是spam,一个是eggs。
字典的编码格式为:d<关键字><值>e,字典以d为起始符,以e为终结符,关键字是一个经过B编码的字符串,值可以是任何合法的B编码类型,在d和e之间可以出现多个关键字和值对,d是dictionary的第一个字母。例如,d4:spaml3:aaa3:bbbee,它是一个字典,该字典的关键字是spam,值是一个列表(以l开始,以e结束),列表中有两个字符串aaa和bbb。又如:d9:publisher3:bob17:publisher-webpage15:www.example.come,它也是一个字典,第一个关键字是publisher,对应的值为bob,第二个关键字是publisher-webpage,对应的值是www.example.com。
2、BT系统结构设计
整个系统各个模块的功能如下。
(1)种子解析:负责解析种子文件,从中获取Tracker服务器的地址,待下载文件的文件名和长度,piece长度,各个piece的hash值。
(2)连接Tracker:根据HTTP协议构造获取Peer地址的请求,与Tracker建立连接,解析Tracker的回应消息,从而获取各个peer的IP地址和端口号。
(3)与peer交换数据:根据peer的IP地址和端口号连接peer、从peer处下载数据并将已下载的数据上传给peer。
(4)出错处理:定义整个系统可能出现的错误类型,并对错误进行处理。
(5)运行日志:记录程序运行的日志,并保存到文件中以备查看和分析。
模块“与peer交换数据”是本系统的核心和主要构成部分,它又可以划分成如下几个子模块。
(1)peer管理:系统为每一个已建立TCP连接的peer构造一个peer结构体。该结构体的主要成员有:peer的IP地址和端口号、与该peer进行通信的套接字、该peer的id、当前所处的状态、发送缓冲区、接收缓冲区、数据请求队列、数据被请求队列、从该peer处已下载的数据量和向该peer上传的数据量、下载速度和上传速度。本模块负责管理peer链表,添加和删除peer结点。
(2)消息处理:peer与peer之间以发送和接收消息的方式进行通信。本模块负责根据当前的状态生成并发送消息,接收并处理消息。BitTorrent协议共定义了12种消息,其中对下载和上传数据最重要的是request消息和piece消息。request消息向peer发送数据请求,指明请求的是哪个piece的哪个slice。Peer接收到request消息后根据当前的状态,决定是否发送数据给对方。如果允许发送,则构造piece消息,数据被封装在该消息中。每当下载完一个正确的piece时,就向所有peer发送have消息通告已获得该piece,其他peer如果没有该piece就可以向peer发送数据请求,每次请求都是以slice为单位。
(3)缓冲管理:如果下载完一个piece就立即写入硬盘,这样会导致频繁读写硬盘,既影响速度(读写磁盘要花费较多的时间),又不利于保护硬盘(频繁读写磁盘会使硬盘寿命缩短)。为了解决这个问题,几乎所有的BT软件都在程序中增加了一个缓冲管理模块。将下载到的数据先缓存起来,等到下载到一定量的数据后再集中写入硬盘。peer请求一个slice的数据时,先将该slice所在的整个piece读入到缓冲区中,下次Peer再请求该piece的其他slice时,只常缓冲区中获取,避免了频繁读写硬盘。本模块负责维护一个16MB的缓冲区(大小可调),将下载到的数据保存在缓冲区中,并在适当时刻写入硬盘的文件中。
(4)位图管理:BT协议采用位图指明当前哪些piece已经下载,哪些piece还没有下载。每个piece占一位,值为0表示该piece还未下载到,为1则表明已经下载到该piece。本模块负责管理位图,客户端与peer建立了连接并进行握手之后,即发送位图给peer告知已下载到哪些piece,同时也接收对方的位图并将其保存在Peer结构体中。每下载到一个piece就更新自己的位图,并发送have消息给所有已建立连接的peer。每当接收到peer发来的have消息就更新该peer的位图。
(5)策略管理:BT协议的设计者为了保证整体性能而制定了许多策略,这些策略虽然没有写入BT协议,但已经成为事实上的标准,BT软件开发者一般都使用这些策略来保证程序的性能。本部分负责策略的管理,主要是计算各个peer的下载和上传速度,根据下载速度选择非阻塞peer,采用随机算法选择优化非阻塞peer,以及实现片断选择策略。
(6)信号处理:在运行过程中,程序可能会接收到一些信号,如SIGINT、SIGTERM,这些信号的默认动作是立即终止程序。这并不是所期望的。在程序终止前需要作一些处理,如释放动态申请的内存、关闭文件描述符、关闭套接字。
3、部分模块代码
(1)种子解析模块
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列表添加一个URLint 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服务器的URLint 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 DEBUGprintf("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;
}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 ) {if( find_keyword("8:announce",&i) == 1 ) {i = i + strlen("8:announce");while( isdigit(metafile_content[i]) ) {len = len * 10 + (metafile_content[i] - '0');i++;}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 DEBUGp = 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);
#endifreturn 0;
}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 DEBUGprintf("piece length:%d\n",piece_length);
#endifreturn 0;
}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 DEBUGprintf("get_pieces ok\n");
#endifreturn 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);
#endifreturn 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 DEBUGprintf("file_length:%lld\n",file_length);
#endifreturn 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;// }
#endifreturn 0;
}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 iif(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 DEBUGprintf("info_hash:");for(i = 0; i < 20; i++) printf("%.2x ",info_hash[i]);printf("\n");
#endifreturn 0;
}int get_peer_id()
{// 设置产生随机数的种子srand(time(NULL));// 生成随机数,并把其中12位赋给peer_id,peer_id前8位固定为-TT1000-sprintf(peer_id,"-TT1000-%12d",rand());#ifdef DEBUGint i;printf("peer_id:");for(i = 0; i < 20; i++) printf("%c",peer_id[i]);printf("\n");
#endifreturn 0;
}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的长度,一般为256KBret = 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_idret = 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;
}
(2)位图管理模块bitfield.h
#ifndef BITFIELD_H
#define BITFIELD_Htypedef struct _Bitmap {unsigned char *bitfield; // 保存位图int bitfield_length; // 位图所占的总字节数int valid_length; // 位图有效的总位数,每一位代表一个piece
} Bitmap;int create_bitfield(); // 创建位图,分配内存并进行初始化
int get_bit_value(Bitmap *bitmap,int index); // 获取某一位的值
int set_bit_value(Bitmap *bitmap,int index,unsigned char value); // 设置某一位的值
int all_zero(Bitmap *bitmap); // 全部清零
int all_set(Bitmap *bitmap); // 全部设置为1
void release_memory_in_bitfield(); // 释放bitfield.c中动态分配的内存
int print_bitfield(Bitmap *bitmap); // 打印位图值,用于调试int restore_bitmap(); // 将位图存储到文件中 // 在下次下载时,先读取该文件获取已经下载的进度
int is_interested(Bitmap *dst,Bitmap *src); // 拥有位图src的peer是否对拥有// dst位图的peer感兴趣
int get_download_piece_num(); // 获取当前已下载到的总piece数#endif
bitfield.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "parse_metafile.h"
#include "bitfield.h"extern int pieces_length;
extern char *file_name;Bitmap *bitmap = NULL; // 指向位图
int download_piece_num = 0; // 当前已下载的piece数 // 如果存在一个位图文件,则读位图文件并把获取的内容保存到bitmap
// 如此一来,就可以实现断点续传,即上次下载的内容不至于丢失
int create_bitfield()
{bitmap = (Bitmap *)malloc(sizeof(Bitmap));if(bitmap == NULL) { printf("allocate memory for bitmap fiailed\n"); return -1;}// pieces_length除以20即为总的piece数bitmap->valid_length = pieces_length / 20;bitmap->bitfield_length = pieces_length / 20 / 8;if( (pieces_length/20) % 8 != 0 ) bitmap->bitfield_length++;bitmap->bitfield = (unsigned char *)malloc(bitmap->bitfield_length);if(bitmap->bitfield == NULL) { printf("allocate memory for bitmap->bitfield fiailed\n"); if(bitmap != NULL) free(bitmap);return -1;}char bitmapfile[64];sprintf(bitmapfile,"%dbitmap",pieces_length);int i;FILE *fp = fopen(bitmapfile,"rb");if(fp == NULL) { // 若打开文件失败,说明开始的是一个全新的下载memset(bitmap->bitfield, 0, bitmap->bitfield_length);} else {fseek(fp,0,SEEK_SET);for(i = 0; i < bitmap->bitfield_length; i++)(bitmap->bitfield)[i] = fgetc(fp);fclose(fp); // 给download_piece_num赋新的初值download_piece_num = get_download_piece_num();}return 0;
}int get_bit_value(Bitmap *bitmap,int index)
{int ret;int byte_index;unsigned char byte_value;unsigned char inner_byte_index;if(index >= bitmap->valid_length) return -1;byte_index = index / 8;byte_value = bitmap->bitfield[byte_index];inner_byte_index = index % 8;byte_value = byte_value >> (7 - inner_byte_index);if(byte_value % 2 == 0) ret = 0;else ret = 1;return ret;
}int set_bit_value(Bitmap *bitmap,int index,unsigned char v)
{int byte_index;unsigned char inner_byte_index;if(index >= bitmap->valid_length) return -1;if((v != 0) && (v != 1)) return -1;byte_index = index / 8;inner_byte_index = index % 8;v = v << (7 - inner_byte_index);bitmap->bitfield[byte_index] = bitmap->bitfield[byte_index] | v;return 0;
}int all_zero(Bitmap *bitmap)
{if(bitmap->bitfield == NULL) return -1;memset(bitmap->bitfield,0,bitmap->bitfield_length);return 0;
}int all_set(Bitmap *bitmap)
{if(bitmap->bitfield == NULL) return -1;memset(bitmap->bitfield,0xff,bitmap->bitfield_length);return 0;
}void release_memory_in_bitfield()
{if(bitmap->bitfield != NULL) free(bitmap->bitfield);if(bitmap != NULL) free(bitmap);
}int print_bitfield(Bitmap *bitmap)
{int i;for(i = 0; i < bitmap->bitfield_length; i++) {printf("%.2X ",bitmap->bitfield[i]);if( (i+1) % 16 == 0) printf("\n");}printf("\n");return 0;
}int restore_bitmap()
{int fd;char bitmapfile[64];if( (bitmap == NULL) || (file_name == NULL) ) return -1;sprintf(bitmapfile,"%dbitmap",pieces_length);fd = open(bitmapfile,O_RDWR|O_CREAT|O_TRUNC,0666);if(fd < 0) return -1;write(fd,bitmap->bitfield,bitmap->bitfield_length);close(fd);return 0;
}int is_interested(Bitmap *dst,Bitmap *src)
{unsigned char const_char[8] = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};unsigned char c1, c2;int i, j;if( dst==NULL || src==NULL ) return -1;if( dst->bitfield==NULL || src->bitfield==NULL ) return -1;if( dst->bitfield_length!=src->bitfield_length ||dst->valid_length!=src->valid_length )return -1;for(i = 0; i < dst->bitfield_length-1; i++) {for(j = 0; j < 8; j++) {c1 = (dst->bitfield)[i] & const_char[j];c2 = (src->bitfield)[i] & const_char[j];if(c1>0 && c2==0) return 1;}}j = dst->valid_length % 8;c1 = dst->bitfield[dst->bitfield_length-1];c2 = src->bitfield[src->bitfield_length-1];for(i = 0; i < j; i++) {if( (c1&const_char[i])>0 && (c2&const_char[i])==0 )return 1;}return 0;
}
/* 以上函数的功能测试代码如下:测试时可以交换map1.bitfield和map2.bitfield的值或赋其他值Bitmap map1, map2;unsigned char bf1[2] = { 0xa0, 0xa0 };unsigned char bf2[2] = { 0xe0, 0xe0 };map1.bitfield = bf1;map1.bitfield_length = 2;map1.valid_length = 11;map2.bitfield = bf2;map2.bitfield_length = 2;map2.valid_length = 11;int ret = is_interested(&map1,&map2); printf("%d\n",ret);*/// 获取当前已下载到的总的piece数
int get_download_piece_num()
{unsigned char const_char[8] = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};int i, j;if(bitmap==NULL || bitmap->bitfield==NULL) return 0;download_piece_num =0;for(i = 0; i < bitmap->bitfield_length-1; i++) {for(j = 0; j < 8; j++) {if( ((bitmap->bitfield)[i] & const_char[j]) != 0) download_piece_num++;}}unsigned char c = (bitmap->bitfield)[i]; // c存放位图最后一个字节j = bitmap->valid_length % 8; // j是位图最后一个字节的有效位数for(i = 0; i < j; i++) {if( (c & const_char[i]) !=0 ) download_piece_num++;}return download_piece_num;
}
(3)运行日志模块log.h
#ifndef LOG_H
#define LOG_H
#include <stdarg.h>// 用于记录程序的行为
void logcmd(char *fmt,...);// 打开日志文件
int init_logfile(char *filename);// 将程序运行日志记录到文件
int logfile(char *file,int line,char *msg);#endif
log.cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include "log.h"int logfile_fd = -1;void logcmd(char *fmt,...)
{va_list ap;va_start(ap,fmt);vprintf(fmt,ap);va_end(ap);
}int init_logfile(char *filename)
{logfile_fd = open(filename,O_RDWR|O_CREAT|O_APPEND,0666);if(logfile_fd < 0) {printf("open logfile failed\n");return -1;}return 0;
}int logfile(char *file,int line,char *msg)
{char buff[256];if(logfile_fd < 0) return -1;snprintf(buff,256,"%s:%d %s\n",file,line,msg);write(logfile_fd,buff,strlen(buff));return 0;
}
(4)信号处理模块
signal_hander.h
#ifndef SIGNAL_HANDER_H
#define SIGNAL_HANDER_H// 做一些清理工作,如释放动态分配的内存
void do_clear_work();// 处理一些信号
void process_signal(int signo);// 设置信号处理函数
int set_signal_hander();#endif
signal_hander.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include "signal_hander.h"
#include "parse_metafile.h"
#include "data.h"
#include "bitfield.h"
#include "peer.h"
#include "tracker.h"
#include "torrent.h"extern int download_piece_num;
extern int *fds;
extern int fds_len;
extern Peer *peer_head;void do_clear_work()
{// 关闭所有peer的socketPeer *p = peer_head;while(p != NULL) {if(p->state != CLOSING) close(p->socket);p = p->next;}// 保存位图if(download_piece_num > 0) {restore_bitmap();}// 关闭文件描述符int i;for(i = 0; i < fds_len; i++) {close(fds[i]);}// 释放动态分配的内存release_memory_in_parse_metafile();release_memory_in_bitfield();release_memory_in_btcache();release_memory_in_peer();release_memory_in_torrent();exit(0);
}void process_signal(int signo)
{printf("Please wait for clear operations\n");do_clear_work();
}int set_signal_hander()
{if(signal(SIGPIPE,SIG_IGN) == SIG_ERR) {perror("can not catch signal:sigpipe\n");return -1;}if(signal(SIGINT,process_signal) == SIG_ERR) {perror("can not catch signal:sigint\n");return -1;}if(signal(SIGTERM,process_signal) == SIG_ERR) {perror("can not catch signal:sigterm\n");return -1;}return 0;
}
(5)Peer管理模块
peer.h
#ifndef PEER_H
#define PEER_H#include <string.h>
#include <time.h>
#include "bitfield.h"#define INITIAL -1 // 表明处于初始化状态
#define HALFSHAKED 0 // 表明处于半握手状态
#define HANDSHAKED 1 // 表明处于全握手状态
#define SENDBITFIELD 2 // 表明处于已发送位图状态
#define RECVBITFIELD 3 // 表明处于已接收位图状态
#define DATA 4 // 表明处于与peer交换数据的状态
#define CLOSING 5 // 表明处于即将与peer断开的状态// 发送和接收缓冲区的大小,16K可以存放一个slice,2K可以存放其他消息
#define MSG_SIZE 2*1024+16*1024typedef struct _Request_piece {int index; // 请求的piece的索引int begin; // 请求的piece的偏移int length; // 请求的长度,一般为16KBstruct _Request_piece *next;
} Request_piece;typedef struct _Peer {int socket; // 通过该socket与peer进行通信char ip[16]; // peer的ip地址unsigned short port; // peer的端口号char id[21]; // peer的idint state; // 当前所处的状态int am_choking; // 是否将peer阻塞int am_interested; // 是否对peer感兴趣int peer_choking; // 是否被peer阻塞int peer_interested; // 是否被peer感兴趣Bitmap bitmap; // 存放peer的位图char *in_buff; // 存放从peer处获取的消息int buff_len; // 缓存区in_buff的长度char *out_msg; // 存放将发送给peer的消息int msg_len; // 缓冲区out_msg的长度char *out_msg_copy; // out_msg的副本,发送时使用该缓冲区int msg_copy_len; // 缓冲区out_msg_copy的长度int msg_copy_index; // 下一次要发送的数据的偏移量Request_piece *Request_piece_head; // 向peer请求数据的队列Request_piece *Requested_piece_head; // 被peer请求数据的队列unsigned int down_total; // 从该peer下载的数据的总和unsigned int up_total; // 向该peer上传的数据的总和time_t start_timestamp; // 最近一次接收到peer消息的时间time_t recet_timestamp; // 最近一次发送消息给peer的时间time_t last_down_timestamp; // 最近下载数据的开始时间time_t last_up_timestamp; // 最近上传数据的开始时间long long down_count; // 本计时周期从peer下载的数据的字节数long long up_count; // 本计时周期向peer上传的数据的字节数float down_rate; // 本计时周期从peer处下载数据的速度float up_rate; // 本计时周期向peer处上传数据的速度struct _Peer *next; // 指向下一个Peer结构体
} Peer;int initialize_peer(Peer *peer); // 对peer进行初始化
Peer* add_peer_node(); // 添加一个peer结点
int del_peer_node(Peer *peer); // 删除一个peer结点
void free_peer_node(Peer *node); // 释放一个peer的内存int cancel_request_list(Peer *node); // 撤消当前请求队列
int cancel_requested_list(Peer *node); // 撤消当前被请求队列void release_memory_in_peer(); // 释放peer.c中的动态分配的内存
void print_peers_data(); // 打印peer链表中某些成员的值,用于调试#endif
peer.c
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "peer.h"
#include "message.h"
#include "bitfield.h"extern Bitmap *bitmap;// 指向当前与之进行通信的peer列表
Peer *peer_head = NULL;int initialize_peer(Peer *peer)
{if(peer == NULL) return -1;peer->socket = -1;memset(peer->ip,0,16);peer->port = 0;memset(peer->id,0,21);peer->state = INITIAL;peer->in_buff = NULL;peer->out_msg = NULL;peer->out_msg_copy = NULL;peer->in_buff = (char *)malloc(MSG_SIZE);if(peer->in_buff == NULL) goto OUT;memset(peer->in_buff,0,MSG_SIZE);peer->buff_len = 0;peer->out_msg = (char *)malloc(MSG_SIZE);if(peer->out_msg == NULL) goto OUT;memset(peer->out_msg,0,MSG_SIZE);peer->msg_len = 0;peer->out_msg_copy = (char *)malloc(MSG_SIZE);if(peer->out_msg_copy == NULL) goto OUT;memset(peer->out_msg_copy,0,MSG_SIZE);peer->msg_copy_len = 0;peer->msg_copy_index = 0;peer->am_choking = 1;peer->am_interested = 0;peer->peer_choking = 1;peer->peer_interested = 0;peer->bitmap.bitfield = NULL;peer->bitmap.bitfield_length = 0;peer->bitmap.valid_length = 0;peer->Request_piece_head = NULL;peer->Requested_piece_head = NULL;peer->down_total = 0;peer->up_total = 0;peer->start_timestamp = 0;peer->recet_timestamp = 0;peer->last_down_timestamp = 0;peer->last_up_timestamp = 0;peer->down_count = 0;peer->up_count = 0;peer->down_rate = 0.0;peer->up_rate = 0.0;peer->next = (Peer *)0;return 0;OUT:if(peer->in_buff != NULL) free(peer->in_buff);if(peer->out_msg != NULL) free(peer->out_msg);if(peer->out_msg_copy != NULL) free(peer->out_msg_copy);return -1;
}Peer* add_peer_node()
{int ret;Peer *node, *p;// 分配内存空间node = (Peer *)malloc(sizeof(Peer));if(node == NULL) { printf("%s:%d error\n",__FILE__,__LINE__); return NULL;}// 进行初始化ret = initialize_peer(node);if(ret < 0) { printf("%s:%d error\n",__FILE__,__LINE__);free(node);return NULL;}// 将node加入到peer链表中if(peer_head == NULL) { peer_head = node; }else {p = peer_head;while(p->next != NULL) p = p->next;p->next = node;}return node;
}int del_peer_node(Peer *peer)
{Peer *p = peer_head, *q;if(peer == NULL) return -1;while(p != NULL) {if( p == peer ) {if(p == peer_head) peer_head = p->next;else q->next = p->next;free_peer_node(p); // 可能存在问题return 0;} else {q = p;p = p->next;}}return -1;
}// 撤消当前请求队列
int cancel_request_list(Peer *node)
{Request_piece *p;p = node->Request_piece_head;while(p != NULL) {node->Request_piece_head = node->Request_piece_head->next;free(p);p = node->Request_piece_head;}return 0;
}// 撤消当前被请求队列
int cancel_requested_list(Peer *node)
{Request_piece *p;p = node->Requested_piece_head;while(p != NULL) {node->Requested_piece_head = node->Requested_piece_head->next;free(p);p = node->Requested_piece_head;}return 0;
}void free_peer_node(Peer *node)
{if(node == NULL) return;if(node->bitmap.bitfield != NULL) {free(node->bitmap.bitfield);node->bitmap.bitfield = NULL;}if(node->in_buff != NULL) {free(node->in_buff); node->in_buff = NULL;}if(node->out_msg != NULL) {free(node->out_msg);node->out_msg = NULL;}if(node->out_msg_copy != NULL) {free(node->out_msg_copy);node->out_msg_copy = NULL;}cancel_request_list(node);cancel_requested_list(node);// 释放完peer成员的内存后,再释放peer所占的内存free(node);
}void release_memory_in_peer()
{Peer *p;if(peer_head == NULL) return;p = peer_head;while(p != NULL) {peer_head = peer_head->next;free_peer_node(p);p = peer_head;}
}void print_peers_data()
{Peer *p = peer_head;int index = 0;while(p != NULL) {printf("peer: %d down_rate: %.2f \n", index, p->down_rate);index++;p = p->next;}
}
(6)消息处理模块
message.h
#ifndef MESSAGE_H
#define MESSAGE_H
#include "peer.h"int int_to_char(int i, unsigned char c[4]); // 将整型变量i的四个字节存放到数组c中
int char_to_int(unsigned char c[4]); // 将数组c中的四个字节转换为一个整型数// 以下函数创建各个类型的消息
int create_handshake_msg(char *info_hash,char *peer_id,Peer *peer);
int create_keep_alive_msg(Peer *peer);
int create_chock_interested_msg(int type,Peer *peer);
int create_have_msg(int index,Peer *peer);
int create_bitfield_msg(char *bitfield,int bitfield_len,Peer *peer);
int create_request_msg(int index,int begin,int length,Peer *peer);
int create_piece_msg(int index,int begin,char *block,int b_len,Peer *peer);
int create_cancel_msg(int index,int begin,int length,Peer *peer);
int create_port_msg(int port,Peer *peer);// 打印消息缓冲区中的消息, 用于调试
int print_msg_buffer(unsigned char *buffer, int len);
// 为发送have消息作准备,have消息较为特殊,它要发送给所有peer
int prepare_send_have_msg();
// 判断缓冲区中是否存放了一个完整的消息
int is_complete_message(unsigned char *buff,unsigned int len,int *ok_len);
// 处理收到的消息,接收缓冲区中存放着一个完整的消息
int parse_response(Peer *peer);
// 处理受到的消息,接收缓冲区中除了存放一个完整的消息外,还有不完整的消息
int parse_response_uncomplete_msg(Peer *p,int ok_len);
// 创建响应消息
int create_response_message(Peer *peer);
// 即将与peer断开时,丢弃发送缓冲区中的消息
void discard_send_buffer(Peer *peer);#endif
message.c
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <sys/socket.h>
#include "parse_metafile.h"
#include "bitfield.h"
#include "peer.h"
#include "data.h"
#include "policy.h"
#include "message.h"#define HANDSHAKE -2
#define KEEP_ALIVE -1
#define CHOKE 0
#define UNCHOKE 1
#define INTERESTED 2
#define UNINTERESTED 3
#define HAVE 4
#define BITFIELD 5
#define REQUEST 6
#define PIECE 7
#define CANCEL 8
#define PORT 9#define KEEP_ALIVE_TIME 45extern Bitmap *bitmap;
extern char info_hash[20];
extern char peer_id[20];
extern int have_piece_index[64];
extern Peer *peer_head;int int_to_char(int i, unsigned char c[4])
{c[3] = i%256;c[2] = (i-c[3])/256%256;c[1] = (i-c[3]-c[2]*256)/256/256%256;c[0] = (i-c[3]-c[2]*256-c[1]*256*256)/256/256/256%256;return 0;
}int char_to_int(unsigned char c[4])
{int i;i = c[0]*256*256*256 + c[1]*256*256 + c[2]*256 + c[3];return i;
}int create_handshake_msg(char *info_hash,char *peer_id,Peer *peer)
{int i;unsigned char keyword[20] = "BitTorrent protocol", c = 0x00;unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if(len < 68) return -1; // 68为握手消息的固定长度buffer[0] = 19;for(i = 0; i < 19; i++) buffer[i+1] = keyword[i];for(i = 0; i < 8; i++) buffer[i+20] = c;for(i = 0; i < 20; i++) buffer[i+28] = info_hash[i];for(i = 0; i < 20; i++) buffer[i+48] = peer_id[i];peer->msg_len += 68;return 0;
}int create_keep_alive_msg(Peer *peer)
{unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if(len < 4) return -1; // 4为keep_alive消息的固定长度memset(buffer,0,4);peer->msg_len += 4;return 0;
}int create_chock_interested_msg(int type,Peer *peer)
{unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;// 5为choke、unchoke、interested、uninterested消息的固定长度if(len < 5) return -1;memset(buffer,0,5);buffer[3] = 1;buffer[4] = type;peer->msg_len += 5;return 0;
}int create_have_msg(int index,Peer *peer)
{unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;unsigned char c[4];if(len < 9) return -1; // 9为have消息的固定长度memset(buffer,0,9); buffer[3] = 5;buffer[4] = 4;int_to_char(index,c);buffer[5] = c[0];buffer[6] = c[1];buffer[7] = c[2];buffer[8] = c[3];peer->msg_len += 9;return 0;
}int create_bitfield_msg(char *bitfield,int bitfield_len,Peer *peer)
{int i;unsigned char c[4];unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if( len < bitfield_len+5 ) { // bitfield消息的长度为bitfield_len+5printf("%s:%d buffer too small\n",__FILE__,__LINE__); return -1;}int_to_char(bitfield_len+1,c);for(i = 0; i < 4; i++) buffer[i] = c[i];buffer[4] = 5;for(i = 0; i < bitfield_len; i++) buffer[i+5] = bitfield[i];peer->msg_len += bitfield_len+5; return 0;
}int create_request_msg(int index,int begin,int length,Peer *peer)
{int i;unsigned char c[4];unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if(len < 17) return -1; // 17为request消息的固定长度memset(buffer,0,17);buffer[3] = 13;buffer[4] = 6;int_to_char(index,c);for(i = 0; i < 4; i++) buffer[i+5] = c[i];int_to_char(begin,c);for(i = 0; i < 4; i++) buffer[i+9] = c[i];int_to_char(length,c);for(i = 0; i < 4; i++) buffer[i+13] = c[i];peer->msg_len += 17;return 0;
}int create_piece_msg(int index,int begin,char *block,int b_len,Peer *peer)
{int i;unsigned char c[4];unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if( len < b_len+13 ) { // piece消息的长度为b_len+13printf("IP:%s len:%d\n",peer->ip,len);printf("%s:%d buffer too small\n",__FILE__,__LINE__); return -1;}int_to_char(b_len+9,c);for(i = 0; i < 4; i++) buffer[i] = c[i];buffer[4] = 7;int_to_char(index,c);for(i = 0; i < 4; i++) buffer[i+5] = c[i];int_to_char(begin,c);for(i = 0; i < 4; i++) buffer[i+9] = c[i];for(i = 0; i < b_len; i++) buffer[i+13] = block[i];peer->msg_len += b_len+13; return 0;
}int create_cancel_msg(int index,int begin,int length,Peer *peer)
{int i;unsigned char c[4];unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if(len < 17) return -1; // 17为cancel消息的固定长度memset(buffer,0,17);buffer[3] = 13;buffer[4] = 8;int_to_char(index,c);for(i = 0; i < 4; i++) buffer[i+5] = c[i];int_to_char(begin,c);for(i = 0; i < 4; i++) buffer[i+9] = c[i];int_to_char(length,c);for(i = 0; i < 4; i++) buffer[i+13] = c[i];peer->msg_len += 17; return 0;
}int create_port_msg(int port,Peer *peer)
{unsigned char c[4];unsigned char *buffer = peer->out_msg + peer->msg_len;int len = MSG_SIZE - peer->msg_len;if( len < 7) return 0; // 7为port消息的固定长度memset(buffer,0,7);buffer[3] = 3;buffer[4] = 9;int_to_char(port,c);buffer[5] = c[2];buffer[6] = c[3];peer->msg_len += 7;return 0;
}// 以十六进制的形式打印消息的内容,用于调试
int print_msg_buffer(unsigned char *buffer, int len)
{int i;for(i = 0; i < len; i++) {printf("%.2x ",buffer[i]);if( (i+1) % 16 == 0 ) printf("\n");}printf("\n");return 0;
}// 判断缓冲区中是否存放了一条完整的消息
int is_complete_message(unsigned char *buff,unsigned int len,int *ok_len)
{unsigned int i;char btkeyword[20];unsigned char keep_alive[4] = { 0x0, 0x0, 0x0, 0x0 };unsigned char chocke[5] = { 0x0, 0x0, 0x0, 0x1, 0x0};unsigned char unchocke[5] = { 0x0, 0x0, 0x0, 0x1, 0x1};unsigned char interested[5] = { 0x0, 0x0, 0x0, 0x1, 0x2};unsigned char uninterested[5] = { 0x0, 0x0, 0x0, 0x1, 0x3};unsigned char have[5] = { 0x0, 0x0, 0x0, 0x5, 0x4};unsigned char request[5] = { 0x0, 0x0, 0x0, 0xd, 0x6};unsigned char cancel[5] = { 0x0, 0x0, 0x0, 0xd, 0x8};unsigned char port[5] = { 0x0, 0x0, 0x0, 0x3, 0x9};if(buff==NULL || len<=0 || ok_len==NULL) return -1;*ok_len = 0;btkeyword[0] = 19;memcpy(&btkeyword[1],"BitTorrent protocol",19); // BitTorrent协议关键字unsigned char c[4];unsigned int length;for(i = 0; i < len; ) {// 握手、chocke、have等消息的长度是固定的if( i+68<=len && memcmp(&buff[i],btkeyword,20)==0 ) i += 68;else if( i+4 <=len && memcmp(&buff[i],keep_alive,4)==0 ) i += 4;else if( i+5 <=len && memcmp(&buff[i],chocke,5)==0 ) i += 5;else if( i+5 <=len && memcmp(&buff[i],unchocke,5)==0 ) i += 5;else if( i+5 <=len && memcmp(&buff[i],interested,5)==0 ) i += 5;else if( i+5 <=len && memcmp(&buff[i],uninterested,5)==0 ) i += 5;else if( i+9 <=len && memcmp(&buff[i],have,5)==0 ) i += 9;else if( i+17<=len && memcmp(&buff[i],request,5)==0 ) i += 17;else if( i+17<=len && memcmp(&buff[i],cancel,5)==0 ) i += 17;else if( i+7 <=len && memcmp(&buff[i],port,5)==0 ) i += 7;// bitfield消息的长度是变化的else if( i+5 <=len && buff[i+4]==5 ) {c[0] = buff[i]; c[1] = buff[i+1];c[2] = buff[i+2]; c[3] = buff[i+3];length = char_to_int(c); // 消息长度占4字节,消息本身占length个字节if( i+4+length <= len ) i += 4+length;else { *ok_len = i; return -1; }}// piece消息的长度也是变化的else if( i+5 <=len && buff[i+4]==7 ) {c[0] = buff[i]; c[1] = buff[i+1];c[2] = buff[i+2]; c[3] = buff[i+3];length = char_to_int(c);// 消息长度占4字节,消息本身占length个字节if( i+4+length <= len ) i += 4+length;else { *ok_len = i; return -1; }}else {// 处理未知类型的消息if(i+4 <= len) {c[0] = buff[i]; c[1] = buff[i+1];c[2] = buff[i+2]; c[3] = buff[i+3];length = char_to_int(c);// 消息长度占4字节,消息本身占length个字节if(i+4+length <= len) { i += 4+length; continue; }else { *ok_len = i; return -1; }}// 如果也不是未知消息类型,则认为目前接收的数据还不是一个完整的消息*ok_len = i;return -1;}}*ok_len = i;return 1;
}int process_handshake_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;if(memcmp(info_hash,buff+28,20) != 0) { peer->state = CLOSING;// 丢弃发送缓冲区中的数据discard_send_buffer(peer);clear_btcache_before_peer_close(peer);close(peer->socket);return -1;}memcpy(peer->id,buff+48,20);(peer->id)[20] = '\0';if(peer->state == INITIAL) {peer->state = HANDSHAKED;create_handshake_msg(info_hash,peer_id,peer);}if(peer->state == HALFSHAKED) peer->state = HANDSHAKED;peer->start_timestamp = time(NULL);return 0;
}int process_keep_alive_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;peer->start_timestamp = time(NULL);return 0;
}int process_choke_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;if( peer->state!=CLOSING && peer->peer_choking==0 ) {peer->peer_choking = 1;peer->last_down_timestamp = 0;peer->down_count = 0;peer->down_rate = 0;}peer->start_timestamp = time(NULL);return 0;
}int process_unchoke_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;if( peer->state!=CLOSING && peer->peer_choking==1 ) {peer->peer_choking = 0;if(peer->am_interested == 1) create_req_slice_msg(peer);else {peer->am_interested = is_interested(&(peer->bitmap), bitmap);if(peer->am_interested == 1) create_req_slice_msg(peer);else printf("Received unchoke but Not interested to IP:%s \n",peer->ip);}peer->last_down_timestamp = 0;peer->down_count = 0;peer->down_rate = 0;}peer->start_timestamp = time(NULL);return 0;
}int process_interested_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;if( peer->state!=CLOSING && peer->state==DATA ) {peer->peer_interested = is_interested(bitmap, &(peer->bitmap));if(peer->peer_interested == 0) return -1;if(peer->am_choking == 0) create_chock_interested_msg(1,peer);}peer->start_timestamp = time(NULL);return 0;
}int process_uninterested_msg(Peer *peer,unsigned char *buff,int len)
{if(peer==NULL || buff==NULL) return -1;if( peer->state!=CLOSING && peer->state==DATA ) {peer->peer_interested = 0;cancel_requested_list(peer);}peer->start_timestamp = time(NULL);return 0;
}int process_have_msg(Peer *peer,unsigned char *buff,int len)
{int rand_num;unsigned char c[4];if(peer==NULL || buff==NULL) return -1;srand(time(NULL));rand_num = rand() % 3;if( peer->state!=CLOSING && peer->state==DATA ) {c[0] = buff[5]; c[1] = buff[6];c[2] = buff[7]; c[3] = buff[8]; if(peer->bitmap.bitfield != NULL)set_bit_value(&(peer->bitmap),char_to_int(c),1);if(peer->am_interested == 0) {peer->am_interested = is_interested(&(peer->bitmap), bitmap);// 由原来的对peer不感兴趣变为感兴趣时,发interested消息if(peer->am_interested == 1) create_chock_interested_msg(2,peer); } else { // 收到三个have则发一个interested消息if(rand_num == 0) create_chock_interested_msg(2,peer);}}peer->start_timestamp = time(NULL);return 0;
}int process_cancel_msg(Peer *peer,unsigned char *buff,int len)
{unsigned char c[4];int index, begin, length;if(peer==NULL || buff==NULL) return -1;c[0] = buff[5]; c[1] = buff[6];c[2] = buff[7]; c[3] = buff[8];index = char_to_int(c);c[0] = buff[9]; c[1] = buff[10];c[2] = buff[11]; c[3] = buff[12];begin = char_to_int(c);c[0] = buff[13]; c[1] = buff[14];c[2] = buff[15]; c[3] = buff[16];length = char_to_int(c);Request_piece *p, *q;p = q = peer->Requested_piece_head;while(p != NULL) { if( p->index==index && p->begin==begin && p->length==length ) {if(p == peer->Requested_piece_head) peer->Requested_piece_head = p->next;elseq->next = p->next;free(p);break;}q = p;p = p->next;} peer->start_timestamp = time(NULL);return 0;
}int process_bitfield_msg(Peer *peer,unsigned char *buff,int len)
{unsigned char c[4];if(peer==NULL || buff==NULL) return -1;if(peer->state==HANDSHAKED || peer->state==SENDBITFIELD) {c[0] = buff[0]; c[1] = buff[1];c[2] = buff[2]; c[3] = buff[3]; if( peer->bitmap.bitfield != NULL ) {free(peer->bitmap.bitfield);peer->bitmap.bitfield = NULL;}peer->bitmap.valid_length = bitmap->valid_length;if(bitmap->bitfield_length != char_to_int(c)-1) {peer->state = CLOSING;// 丢弃发送缓冲区中的数据discard_send_buffer(peer);clear_btcache_before_peer_close(peer);close(peer->socket);return -1;}peer->bitmap.bitfield_length = char_to_int(c) - 1;peer->bitmap.bitfield = (unsigned char *)malloc(peer->bitmap.bitfield_length);memcpy(peer->bitmap.bitfield,&buff[5],peer->bitmap.bitfield_length);// 如果原状态为已握手,收到位图后应该向peer发位图if(peer->state == HANDSHAKED) {create_bitfield_msg(bitmap->bitfield,bitmap->bitfield_length,peer);peer->state = DATA;}// 如果原状态为已发送位图,收到位图后可以准备交换数据if(peer->state == SENDBITFIELD) {peer->state = DATA;}// 判断peer是否对我们感兴趣peer->peer_interested = is_interested(bitmap,&(peer->bitmap));// 判断对peer是否感兴趣,若是则发送interested消息peer->am_interested = is_interested(&(peer->bitmap), bitmap);if(peer->am_interested == 1) create_chock_interested_msg(2,peer);}peer->start_timestamp = time(NULL);return 0;
}int process_request_msg(Peer *peer,unsigned char *buff,int len)
{unsigned char c[4];int index, begin, length;Request_piece *request_piece, *p;if(peer==NULL || buff==NULL) return -1;if(peer->am_choking==0 && peer->peer_interested==1) {c[0] = buff[5]; c[1] = buff[6];c[2] = buff[7]; c[3] = buff[8];index = char_to_int(c);c[0] = buff[9]; c[1] = buff[10];c[2] = buff[11]; c[3] = buff[12];begin = char_to_int(c);c[0] = buff[13]; c[1] = buff[14];c[2] = buff[15]; c[3] = buff[16];length = char_to_int(c);// 错误的slice请求if( begin%(16*1024) != 0 ) {return 0;}// 查看该请求是否已存在,若已存在,则不进行处理p = peer->Requested_piece_head;while(p != NULL) {if(p->index==index && p->begin==begin && p->length==length) {break;}p = p->next;}if(p != NULL) return 0;// 将请求加入到请求队列中request_piece = (Request_piece *)malloc(sizeof(Request_piece));if(request_piece == NULL) { printf("%s:%d error",__FILE__,__LINE__); return 0; }request_piece->index = index;request_piece->begin = begin;request_piece->length = length;request_piece->next = NULL;if( peer->Requested_piece_head == NULL ) peer->Requested_piece_head = request_piece;else {p = peer->Requested_piece_head;while(p->next != NULL) p = p->next;p->next = request_piece;}//printf("*** add a request FROM IP:%s index:%-6d begin:%-6x ***\n",// peer->ip,index,begin);}peer->start_timestamp = time(NULL);return 0;
}int process_piece_msg(Peer *peer,unsigned char *buff,int len)
{unsigned char c[4];int index, begin, length;Request_piece *p;if(peer==NULL || buff==NULL) return -1;if(peer->peer_choking==0) {c[0] = buff[0]; c[1] = buff[1];c[2] = buff[2]; c[3] = buff[3];length = char_to_int(c) - 9;c[0] = buff[5]; c[1] = buff[6];c[2] = buff[7]; c[3] = buff[8];index = char_to_int(c);c[0] = buff[9]; c[1] = buff[10];c[2] = buff[11]; c[3] = buff[12];begin = char_to_int(c);p = peer->Request_piece_head;while(p != NULL) {if(p->index==index && p->begin==begin && p->length==length)break;p = p->next;}if(p == NULL) {printf("did not found matched request\n"); return -1;}if(peer->last_down_timestamp == 0)peer->last_down_timestamp = time(NULL);peer->down_count += length;peer->down_total += length;write_slice_to_btcache(index,begin,length,buff+13,length,peer);create_req_slice_msg(peer);}peer->start_timestamp = time(NULL);return 0;
}int parse_response(Peer *peer)
{unsigned char btkeyword[20];unsigned char keep_alive[4] = { 0x0, 0x0, 0x0, 0x0 };int index;unsigned char *buff = peer->in_buff;int len = peer->buff_len;if(buff==NULL || peer==NULL) return -1;btkeyword[0] = 19;memcpy(&btkeyword[1],"BitTorrent protocol",19); // BitTorrent协议关键字// 分别处理12种消息for(index = 0; index < len; ) { if( (len-index >= 68) && (memcmp(&buff[index],btkeyword,20) == 0) ) {process_handshake_msg(peer,buff+index,68);index += 68;} else if( (len-index >= 4) && (memcmp(&buff[index],keep_alive,4) == 0)){process_keep_alive_msg(peer,buff+index,4);index += 4; }else if( (len-index >= 5) && (buff[index+4] == CHOKE) ) {process_choke_msg(peer,buff+index,5);index += 5;}else if( (len-index >= 5) && (buff[index+4] == UNCHOKE) ) {process_unchoke_msg(peer,buff+index,5);index += 5;}else if( (len-index >= 5) && (buff[index+4] == INTERESTED) ) {process_interested_msg(peer,buff+index,5);index += 5;}else if( (len-index >= 5) && (buff[index+4] == UNINTERESTED) ) {process_uninterested_msg(peer,buff+index,5);index += 5;}else if( (len-index >= 9) && (buff[index+4] == HAVE) ) {process_have_msg(peer,buff+index,9);index += 9;}else if( (len-index >= 5) && (buff[index+4] == BITFIELD) ) {process_bitfield_msg(peer,buff+index,peer->bitmap.bitfield_length+5);index += peer->bitmap.bitfield_length + 5;}else if( (len-index >= 17) && (buff[index+4] == REQUEST) ) {process_request_msg(peer,buff+index,17);index += 17;}else if( (len-index >= 13) && (buff[index+4] == PIECE) ) {unsigned char c[4];int length;c[0] = buff[index]; c[1] = buff[index+1];c[2] = buff[index+2]; c[3] = buff[index+3];length = char_to_int(c) - 9;process_piece_msg(peer,buff+index,length+13);index += length + 13; // length+13为piece消息的长度}else if( (len-index >= 17) && (buff[index+4] == CANCEL) ) {process_cancel_msg(peer,buff+index,17);index += 17;}else if( (len-index >= 7) && (buff[index+4] == PORT) ) {index += 7;}else {// 如果是未知的消息类型,则跳过不予处理unsigned char c[4];int length;if(index+4 <= len) {c[0] = buff[index]; c[1] = buff[index+1];c[2] = buff[index+2]; c[3] = buff[index+3];length = char_to_int(c);if(index+4+length <= len) { index += 4+length; continue; }}// 如果是一条错误的消息,清空接收缓冲区peer->buff_len = 0;return -1;}} // end for// 接收缓冲区中的消息处理完毕后,清空接收缓冲区peer->buff_len = 0;return 0;
}int parse_response_uncomplete_msg(Peer *p,int ok_len)
{char *tmp_buff;int tmp_buff_len;// 分配存储空间,并保存接收缓冲区中不完整的消息tmp_buff_len = p->buff_len - ok_len;if(tmp_buff_len <= 0) return -1;tmp_buff = (char *)malloc(tmp_buff_len);if(tmp_buff == NULL) {printf("%s:%d error\n",__FILE__,__LINE__);return -1;}memcpy(tmp_buff,p->in_buff+ok_len,tmp_buff_len);// 处理接收缓冲区中前面完整的消息p->buff_len = ok_len;parse_response(p);// 将不完整的消息拷贝到接收缓冲区的开始处memcpy(p->in_buff,tmp_buff,tmp_buff_len);p->buff_len = tmp_buff_len;if(tmp_buff != NULL) free(tmp_buff);return 0;
}// 当下载完一个piece时,应该向所有的peer发送have消息
int prepare_send_have_msg()
{Peer *p = peer_head;int i;if(peer_head == NULL) return -1;if(have_piece_index[0] == -1) return -1;while(p != NULL) {for(i = 0; i < 64; i++) {if(have_piece_index[i] != -1) {create_have_msg(have_piece_index[i],p);}else {break;}}p = p->next;}for(i = 0; i < 64; i++) {if(have_piece_index[i] == -1) {break;}else {have_piece_index[i] = -1;}}return 0;
}// 主动创建发送给peer的消息,而不是等收到某个消息再作出响应
int create_response_message(Peer *peer)
{if(peer==NULL) return -1;if(peer->state == INITIAL) {create_handshake_msg(info_hash,peer_id,peer);peer->state = HALFSHAKED;return 0;}if(peer->state == HANDSHAKED) {if(bitmap == NULL) return -1;create_bitfield_msg(bitmap->bitfield,bitmap->bitfield_length,peer);peer->state = SENDBITFIELD;return 0;}// 发送piece消息,即发送下载文件的内容if( peer->am_choking==0 && peer->Requested_piece_head!=NULL ) {Request_piece *req_p = peer->Requested_piece_head;int ret = read_slice_for_send(req_p->index,req_p->begin,req_p->length,peer);if(ret < 0 ) { printf("read_slice_for_send ERROR\n");}else {if(peer->last_up_timestamp == 0) peer->last_up_timestamp = time(NULL);peer->up_count += req_p->length;peer->up_total += req_p->length;peer->Requested_piece_head = req_p->next;//printf("********* sending a slice TO:%s index:%-5d begin:%-5x *********\n",//peer->ip,req_p->index,req_p->begin);free(req_p);return 0;}}// 如果3分钟没有收到任何消息关闭连接time_t now = time(NULL); // 获取当前时间long interval1 = now - peer->start_timestamp;if( interval1 > 180 ) {peer->state = CLOSING;discard_send_buffer(peer); // 丢弃发送缓冲区中的数据clear_btcache_before_peer_close(peer);close(peer->socket);}// 如果45秒没有发送和接收消息,则发送一个keep_alive消息long interval2 = now - peer->recet_timestamp;if( interval1>45 && interval2>45 && peer->msg_len==0)create_keep_alive_msg(peer);return 0;
}void discard_send_buffer(Peer *peer)
{struct linger lin;int lin_len;lin.l_onoff = 1;lin.l_linger = 0;lin_len = sizeof(lin);if(peer->socket > 0) {setsockopt(peer->socket,SOL_SOCKET,SO_LINGER,(char *)&lin,lin_len);}
}
(7)其他模块见完整代码
(8)main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include "data.h"
#include "tracker.h"
#include "bitfield.h"
#include "torrent.h"
#include "parse_metafile.h"
#include "signal_hander.h"
#include "policy.h"
#include "log.h"// #define DEBUGint main(int argc, char *argv[])
{int ret;if(argc != 2) {printf("usage:%s metafile\n",argv[0]);exit(-1);}// 设置信号处理函数ret = set_signal_hander();if(ret != 0) { printf("%s:%d error\n",__FILE__,__LINE__); return -1; }// 解析种子文件ret = parse_metafile(argv[1]);if(ret != 0) { printf("%s:%d error\n",__FILE__,__LINE__); return -1; }// 初始化非阻塞peerinit_unchoke_peers();// 创建用于保存下载数据的文件ret = create_files();if(ret != 0) { printf("%s:%d error\n",__FILE__,__LINE__); return -1; }// 创建位图ret = create_bitfield();if(ret != 0) { printf("%s:%d error\n",__FILE__,__LINE__); return -1; }// 创建缓冲区ret = create_btcache();if(ret != 0) { printf("%s:%d error\n",__FILE__,__LINE__); return -1; }// 负责与所有Peer收发数据、交换消息download_upload_with_peers();// 做一些清理工作,主要是释放动态分配的内存do_clear_work();return 0;
}
(9)makefile
CC=gcc
CFLAGS= -Iinclude -Wall -g -DDEBUG
LDFLAGS=-L./lib -Wl,-rpath=./lib -Wl,-rpath=/usr/local/libttorrent: main.o parse_metafile.o tracker.o bitfield.o sha1.o message.o peer.o data.o policy.o torrent.o bterror.o log.o signal_hander.o$(CC) -o $@ $(LDFLAGS) $^ -ldlclean:rm -rf *.o ttorrent
4、编译运行
$make
$ ./ttorrent Bt_Music.torrent
在终端下执行make生成可执行程序ttorrent,下载到种子文件torrent。
在可执行程序的当前目录下会出现下载的文件:
三、总结
(1)上述内容均来自以前书籍,仅是为了保存记忆以文字形式记录于此。
(2)若有建议,请留言,在此先感谢!
linux下c/c++实例之十四c实现的bt软件下载(记录)相关推荐
- linux下c/c++实例之十四c实现的bt软件下载
转载之http://m.blog.csdn.net/taiyang1987912/article/details/51288661
- linux下c/c++实例之十socket简单应用
转自:http://blog.csdn.net/taiyang1987912/article/details/49738351 一.简介 通过socket扫描本机打开的tcp端口号,模拟用户名.密码登 ...
- python 分数序列求和公式_Python分数序列求和,编程练习题实例二十四
本文是关于Python分数序列求和的应用练习,适合菜鸟练习使用,python大牛绕行哦. Python练习题问题如下: 问题简述:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13 要 ...
- Linux企业运维——Kubernetes(十四)PSP安全策略
Linux企业运维--Kubernetes(十四)PSP安全策略 文章目录 Linux企业运维--Kubernetes(十四)PSP安全策略 一.PSP安全策略简介 二.PSP安全策略配置 一.PSP ...
- linux数据同步技术比较,linux下实现web数据同步的四种方式(性能比较)教程.docx
linux下实现web数据同步的四种方式(性能比较)教程 实现web数据同步的四种方式=======================================1.nfs实现web数据共享2.rs ...
- 「docker实战篇」python的docker爬虫技术-在linux下mitmproxy介绍和安装(四)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:「docker实战篇」python的docker爬虫技术-在linux下mitmproxy介绍和安装(四) 上次说了fiddl ...
- [视觉Slam十四讲(2)踩坑记录]第3讲:Fatal error :Eigen/core没有那个文件或目录
[视觉Slam十四讲(2)踩坑记录]第3讲:Fatal error :Eigen/core没有那个文件或目录 问题描述: 在学习SlamBook2-ch3中对Eigen矩阵运算包内容时,编写好相关代码 ...
- 第十四届蓝桥杯大赛软件赛省赛JavaB组解析
目录 说在前面 试题 A: 阶乘求和 代码: 题目分析: 试题 B: 幸运数字 代码: 题目分析: 试题 D: 矩形总面积 代码: 题目分析: 试题 G: 买二赠一 代码: 题目分析: 试题 H: 合 ...
- Git使用 从入门到入土 收藏吃灰系列 (十四) 清除git仓库的所有提交记录
文章目录 一.前言 二.清除git仓库的所有提交记录 本节速览 清除git仓库的所有提交记录 一.前言 参考安装Git 详细安装教程 参考视频B站 Git最新教程通俗易懂,这个有点长,感觉讲的精华不多 ...
- Linux下定时切割Mongodb数据库日志并删除指定天数前的日志记录(转)
文章转自:http://www.osyunwei.com/archives/8998.html 说明: 操作系统:CentOS Mongodb安装目录:/usr/local/mongodb Mongo ...
最新文章
- 通过注册表开启“终端服务”
- python分析excel数据-总结:像Excel一样使用python进行数据分析
- Docker镜像加速器配置
- UOS系统下FFmpeg源码编译安装及注意事项
- windows oracle 内存大,如何设计Oracle数据库内存的大小
- c语言程序设计的日志,C语言程序设计教学日志.docx
- 15分钟,教你用Python爬网站数据,并用BI可视化分析!
- 上海市社会保险费缴费标准
- 我爱计算机视觉干货集锦分类汇总(2019年6月17日)
- 扫描软件(to be followed)
- linux 网卡bond
- matlab的控制系统仿真,MATLAB控制系统仿真教程
- css td中画斜线,css 模拟表格斜线
- 华为手机百度云息屏后停止下载_让客厅成为娱乐中心,华为智慧屏S系列轻松就能做到...
- css实现背景动态效果图
- BUUCTF:[SWPU2019]神奇的二维码
- 【Debug日记】fatal: cannot lock ref ‘refs/heads/feature/CXX_Config‘: ‘refs/heads/feature‘ exists;
- Spring之IOC本质简述
- ChatGPT初体验——震撼,好用,贾维斯已来
- shopex后台的安装及模板的安装