一、简介

可能许多人使用过比特彗星(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软件下载(记录)相关推荐

  1. linux下c/c++实例之十四c实现的bt软件下载

    转载之http://m.blog.csdn.net/taiyang1987912/article/details/51288661

  2. linux下c/c++实例之十socket简单应用

    转自:http://blog.csdn.net/taiyang1987912/article/details/49738351 一.简介 通过socket扫描本机打开的tcp端口号,模拟用户名.密码登 ...

  3. python 分数序列求和公式_Python分数序列求和,编程练习题实例二十四

    本文是关于Python分数序列求和的应用练习,适合菜鸟练习使用,python大牛绕行哦. Python练习题问题如下: 问题简述:有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13 要 ...

  4. Linux企业运维——Kubernetes(十四)PSP安全策略

    Linux企业运维--Kubernetes(十四)PSP安全策略 文章目录 Linux企业运维--Kubernetes(十四)PSP安全策略 一.PSP安全策略简介 二.PSP安全策略配置 一.PSP ...

  5. linux数据同步技术比较,linux下实现web数据同步的四种方式(性能比较)教程.docx

    linux下实现web数据同步的四种方式(性能比较)教程 实现web数据同步的四种方式=======================================1.nfs实现web数据共享2.rs ...

  6. 「docker实战篇」python的docker爬虫技术-在linux下mitmproxy介绍和安装(四)

    原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:「docker实战篇」python的docker爬虫技术-在linux下mitmproxy介绍和安装(四) 上次说了fiddl ...

  7. [视觉Slam十四讲(2)踩坑记录]第3讲:Fatal error :Eigen/core没有那个文件或目录

    [视觉Slam十四讲(2)踩坑记录]第3讲:Fatal error :Eigen/core没有那个文件或目录 问题描述: 在学习SlamBook2-ch3中对Eigen矩阵运算包内容时,编写好相关代码 ...

  8. 第十四届蓝桥杯大赛软件赛省赛JavaB组解析

    目录 说在前面 试题 A: 阶乘求和 代码: 题目分析: 试题 B: 幸运数字 代码: 题目分析: 试题 D: 矩形总面积 代码: 题目分析: 试题 G: 买二赠一 代码: 题目分析: 试题 H: 合 ...

  9. Git使用 从入门到入土 收藏吃灰系列 (十四) 清除git仓库的所有提交记录

    文章目录 一.前言 二.清除git仓库的所有提交记录 本节速览 清除git仓库的所有提交记录 一.前言 参考安装Git 详细安装教程 参考视频B站 Git最新教程通俗易懂,这个有点长,感觉讲的精华不多 ...

  10. Linux下定时切割Mongodb数据库日志并删除指定天数前的日志记录(转)

    文章转自:http://www.osyunwei.com/archives/8998.html 说明: 操作系统:CentOS Mongodb安装目录:/usr/local/mongodb Mongo ...

最新文章

  1. 通过注册表开启“终端服务”
  2. python分析excel数据-总结:像Excel一样使用python进行数据分析
  3. Docker镜像加速器配置
  4. UOS系统下FFmpeg源码编译安装及注意事项
  5. windows oracle 内存大,如何设计Oracle数据库内存的大小
  6. c语言程序设计的日志,C语言程序设计教学日志.docx
  7. 15分钟,教你用Python爬网站数据,并用BI可视化分析!
  8. 上海市社会保险费缴费标准
  9. 我爱计算机视觉干货集锦分类汇总(2019年6月17日)
  10. 扫描软件(to be followed)
  11. linux 网卡bond
  12. matlab的控制系统仿真,MATLAB控制系统仿真教程
  13. css td中画斜线,css 模拟表格斜线
  14. 华为手机百度云息屏后停止下载_让客厅成为娱乐中心,华为智慧屏S系列轻松就能做到...
  15. css实现背景动态效果图
  16. BUUCTF:[SWPU2019]神奇的二维码
  17. 【Debug日记】fatal: cannot lock ref ‘refs/heads/feature/CXX_Config‘: ‘refs/heads/feature‘ exists;
  18. Spring之IOC本质简述
  19. ChatGPT初体验——震撼,好用,贾维斯已来
  20. shopex后台的安装及模板的安装

热门文章

  1. 使用PostMan调试webService接口
  2. 个人收集资料分享(电子、计算机相关)
  3. linux mpeg4ip 编译,利用Linux实现MPEG4流媒体技术
  4. 快速排序之螺钉螺母匹配
  5. IT近年最新技术一览
  6. 袁亚湘院士谈如何做好研究生:如果不思考,人再聪明也无济于事
  7. 新能源汽车行业资讯-2022-9-19
  8. SHFileOperation 文件拷贝、移动、删除等操作
  9. linux计划任务踩坑
  10. r5驱动 索尼exmor_SONY的驱动安装顺序(还不知道的赶快进来看看!!)