Linux平台下基于BitTorrent应用层协议的下载软件开发--tracker服务器交互模块(tracker.c)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "parse_metafile.h"
#include "peer.h"
#include "tracker.h"

extern unsigned char  info_hash[20];//这两个数据都是在种子解析文件中定义的
extern unsigned char  peer_id[20];
extern Announce_list  *announce_list_head;//为存放各个Tracker的URL

extern int                 *sock;//连接Tracker的套接字
extern struct sockaddr_in  *tracker;
extern int                 *valid;//traker服务器的状态
extern int                  tracker_count;//tracker服务器的状态

extern int                 *peer_sock;//链接peer的套接字
extern struct sockaddr_in  *peer_addr;
extern int                 *peer_valid;//指示连接peer的状态
extern int                  peer_count;//尝试与多少个peer建立连接

Peer_addr  *peer_addr_head = NULL;//根据多少个Tracker服务器,建立多少个peer_addr

int http_encode(unsigned char *in,int len1,char *out,int len2)//http协议说明,非数字和字母都要进行装换
{                                                            //函数参数为一个是转换输入,另一个为转换输出
	int  i, j;
	char hex_table[16] = "0123456789abcdef"; 
	
	if( (len1 != 20) || (len2 <= 90) )  return -1;
	for(i = 0, j = 0; i < 20; i++, j++) { //如果len1不等于20或者len2小于等于90,则出错
		if( isalpha(in[i]) || isdigit(in[i]) )
			out[j] = in[i];
		else { 
		        out[j] = '%';//这是编码部分,至于为何是20与90,不太清楚
			j++;
			out[j] = hex_table[in[i] >> 4];
			j++;
			out[j] = hex_table[in[i] & 0xf];
		}
	}
	out[j] = '\0';
	
#ifdef DEBUG
	//printf("http encoded:%s\n",out);
#endif
	
	return 0;
}

int get_tracker_name(Announce_list *node,char *name,int len)
{
	int i = 0, j = 0;

	if( (len < 64) || (node == NULL) )  return -1;
	if( memcmp(node->announce,"http://",7) == 0 ) 
		i = i + 7;
	while( (node->announce[i] != '/') && (node->announce[i] != ':') ) {//因为本书就是要得到主机的名字
	        name[j] = node->announce[i];//将主机名写入name中
		i++;
		j++;
		if( i == strlen(node->announce) )  break;//这种情况下说明其URL中没有‘/’以及‘:’所以才要靠这个来判断
	}
	name[j] = '\0';

#ifdef DEBUG
	printf("%s\n",node->announce);
	printf("tracker name:%s\n",name);
#endif

	return 0;
}

int get_tracker_port(Announce_list *node,unsigned short *port)
{
	int i = 0;

	if( (node == NULL) || (port == NULL) )  return -1;
	if( memcmp(node->announce,"http://",7) == 0 )  i = i + 7;
	*port = 0;
	while( i < strlen(node->announce) ) {
		if( node->announce[i] != ':')   { i++; continue; }

		i++;  // skip ':'
		while( isdigit(node->announce[i]) ) { 
		  *port =  *port * 10 + (node->announce[i] - '0');//如果判断为数字,则求出其端口号
			i++;
		}
		break;
	}
	if(*port == 0)  *port = 80;

#ifdef DEBUG
	printf("tracker port:%d\n",*port);
#endif

	return 0;
}

int create_request(char *request,int len,Announce_list *node,
				   unsigned short port,long long down,long long up,
				   long long left,int numwant)
{
	char           encoded_info_hash[100];
	char           encoded_peer_id[100];
	int            key;
	char           tracker_name[128];
	unsigned short tracker_port;

	http_encode(info_hash,20,encoded_info_hash,100);//这就可以解释为何输入字节数为20了,因为它为info_hash或者为peer_id的长度
	http_encode(peer_id,20,encoded_peer_id,100);

	srand(time(NULL));
	key = rand() / 10000;//得到随机的key值

	get_tracker_name(node,tracker_name,128);
	get_tracker_port(node,&tracker_port);

	sprintf(request,
	"GET /announce?info_hash=%s&peer_id=%s&port=%u"
	"&uploaded=%lld&downloaded=%lld&left=%lld"
	"&event=started&key=%d&compact=1&numwant=%d HTTP/1.0\r\n"
	"Host: %s\r\nUser-Agent: Bittorrent\r\nAccept: */*\r\n"
	"Accept-Encoding: gzip\r\nConnection: closed\r\n\r\n", //包含上传数据量,以及下载数据量,left为还有多少剩余,numwant,表示希望返回的peer数
	encoded_info_hash,encoded_peer_id,port,up,down,left,
		key,numwant,tracker_name);//key关键字一般用来标识客户端,但是已经有了peer_id,所以这个参数可选

#ifdef DEBUG
	printf("request:%s\n",request);
#endif

	return 0;
}

int get_response_type(char *buffer,int len,int *total_length)//这里的total_length用于在之后的调用函数中分配内存
{
	int i, content_length = 0;

	for(i = 0; i < len-7; i++) {
		if(memcmp(&buffer[i],"5:peers",7) == 0) { 
			i = i+7;
			break; //相当于在buffer所指向的字符串数组中查找‘5:peers’,如果下标=len-7,则buffer中没有机会存储“5:peers”字符串
		}
	}
	if(i == len-7)        return -1;  // 返回的消息不含"5:peers"关键字
	if(buffer[i] != 'l')  return 0;   //这说明消息并不是B编码的列表,所以返回的消息的类型为第一种

	*total_length = 0;
	for(i = 0; i < len-16; i++) {
		if(memcmp(&buffer[i],"Content-Length: ",16) == 0) {
			i = i+16;
			break; 
		}
	}
	if(i != len-16) {//用于判断是否找到content_length,不相等,说明找到
		while(isdigit(buffer[i])) {
		  content_length = content_length * 10 + (buffer[i] - '0');//用于计算content_length
			i++;
		}
		for(i = 0; i < len-4; i++) {//用于判断是否找到“\r\n\r\n”
		  if(memcmp(&buffer[i],"\r\n\r\n",4) == 0)  { i = i+4; break; }//用于计算total_length
		}
		if(i != len-4)  *total_length = content_length + i;
	}

	if(*total_length == 0)  return -1;
	else return 1;
}

int prepare_connect_tracker(int *max_sockfd)
{
	int             i, flags, ret, count = 0;
	struct hostent  *ht;//通过gethostbyname 返回的类型
	/* hostent的定义如下:
           struct hostent {
                  char *h_name;
                  char **h_aliases;
                  int h_addrtype;
                  int h_length;
                  char **h_addr_list;
                  #define h_addr h_addr_list[0]
                };

        h_name – 地址的正式名称。
        h_aliases – 空字节-地址的预备名称的指针。
        h_addrtype –地址类型; 通常是AF_INET。  
        h_length – 地址的比特长度。
        h_addr_list – 零字节-主机网络地址指针。网络字节顺序。
        h_addr - h_addr_list中的第一地址*/
 
	Announce_list   *p = announce_list_head;

	while(p != NULL)  { count++; p = p->next; }
	tracker_count = count; //得到Tracker服务器的个数
	sock = (int *)malloc(count * sizeof(int));//有多少个Tracker服务器就有多少个套接字
	if(sock == NULL)  goto OUT;
	tracker = (struct sockaddr_in *)malloc(count * sizeof(struct sockaddr_in));//用来保存地址信息
	if(tracker == NULL)  goto OUT;
	valid = (int *)malloc(count * sizeof(int));//用来查看是否有效
	if(valid == NULL)  goto OUT;
	
	p = announce_list_head;
	for(i = 0; i < count; i++) {
		char            tracker_name[128];
		unsigned short  tracker_port = 0;
		
		sock[i] = socket(AF_INET,SOCK_STREAM,0);//创建TCP套接字,针对Tracker地址
		if(sock < 0) {
			printf("%s:%d socket create failed\n",__FILE__,__LINE__);
			valid[i] = 0;
			p = p->next;
			continue;
		}

		get_tracker_name(p,tracker_name,128);
		get_tracker_port(p,&tracker_port);
		
		// 从主机名获取IP地址
		ht = gethostbyname(tracker_name);
		if(ht == NULL) {
		        printf("gethostbyname failed:%s\n",hstrerror(h_errno)); //如果出错的话,错误信息是写入h_errno当中的
			valid[i] = 0;
		} else {
			memset(&tracker[i], 0, sizeof(struct sockaddr_in));
			memcpy(&tracker[i].sin_addr.s_addr, ht->h_addr_list[0], 4);//将得到的地址信息存储到多对应的tracker结构中
			tracker[i].sin_port = htons(tracker_port);//设置端口号以及相关参数
			tracker[i].sin_family = AF_INET;
			valid[i] = -1;//此时的这个状态还没有建立链接
		}
		
		p = p->next;//对每一个Tracker服务器,都构建一个套接字
	}

	for(i = 0; i < tracker_count; i++) {
		if(valid[i] != 0) {
			if(sock[i] > *max_sockfd) *max_sockfd = sock[i];
			// 设置套接字为非阻塞
			flags = fcntl(sock[i],F_GETFL,0);//关于fcntl的相关设置为P163值,关键在获得文件打开的方式,成功返回标志值
			fcntl(sock[i],F_SETFL,flags|O_NONBLOCK);//设置文件的打开方式为第三个参数指定的方式,等于原打开方式加上非阻塞方式
			// 连接tracker
			ret = connect(sock[i],(struct sockaddr *)&tracker[i], //有套接字,并且有相关的Tracker地址信息,那么就可以建立连接
				          sizeof(struct sockaddr));
			if(ret < 0 && errno != EINPROGRESS)  valid[i] = 0;	//等于0,说明链接建立失败
			// 如果返回0,说明连接已经建立
			if(ret == 0)  valid[i] = 1;  //只有在全部建立完成之后,才会叫valid设置为1
		}
	}

	return 0;

OUT:
	if(sock != NULL)    free(sock);
	if(tracker != NULL) free(tracker);
	if(valid != NULL)   free(valid);
	return -1;
}

//以非阻塞的方式来链接peer。下面的这个函数与prepare_connect_tracker基本上一样
int prepare_connect_peer(int *max_sockfd)//max_sockfd为最大套接字的值
{
	int       i, flags, ret, count = 0;
	Peer_addr *p;
	
	p = peer_addr_head;//这个时候peer_addr_head的值为NULL
	while(p != 0)  { count++; p = p->next; }

	peer_count = count;
	peer_sock = (int *)malloc(count*sizeof(int));//这边所有的sock都加上了前缀peer
	if(peer_sock == NULL)  goto OUT;
	peer_addr = (struct sockaddr_in *)malloc(count*sizeof(struct sockaddr_in));
	if(peer_addr == NULL)  goto OUT;
	peer_valid = (int *)malloc(count*sizeof(int));
	if(peer_valid == NULL) goto OUT;
	
	p = peer_addr_head;  // 此处p重新赋值
	for(i = 0; i < count && p != NULL; i++) {
		peer_sock[i] = socket(AF_INET,SOCK_STREAM,0);
		if(peer_sock[i] < 0) { 
			printf("%s:%d socket create failed\n",__FILE__,__LINE__);
			valid[i] = 0;
			p = p->next;
			continue; 
		}

		memset(&peer_addr[i], 0, sizeof(struct sockaddr_in));
		peer_addr[i].sin_addr.s_addr = inet_addr(p->ip);//因为p的类型为Peer_addr,所以其包含ip地址信息
		peer_addr[i].sin_port = htons(p->port);
		peer_addr[i].sin_family = AF_INET;
		peer_valid[i] = -1;
		
		p = p->next;
	}
	count = i;
	
	for(i = 0; i < count; i++) {
		if(peer_sock[i] > *max_sockfd) *max_sockfd = peer_sock[i];
		// 设置套接字为非阻塞
		flags = fcntl(peer_sock[i],F_GETFL,0);//获得文件的打开方式
		fcntl(peer_sock[i],F_SETFL,flags|O_NONBLOCK);//重新设置文件的打开方式,加上非阻塞
		// 连接peer
		ret = connect(peer_sock[i],(struct sockaddr *)&peer_addr[i],
			          sizeof(struct sockaddr));
		if(ret < 0 && errno != EINPROGRESS)  peer_valid[i] = 0;
		// 如果返回0,说明连接已经建立
		if(ret == 0)  peer_valid[i] = 1;
	}
	
	free_peer_addr_head();
	return 0;

OUT:
	if(peer_sock  != NULL)  free(peer_sock);
	if(peer_addr  != NULL)  free(peer_addr);
	if(peer_valid != NULL)  free(peer_valid);
	return -1;
}

int parse_tracker_response1(char *buffer,int ret,char *redirection,int len)//对Tracker服务器返回的第一种类型的消息解析
{
	int           i, j, count = 0;
	unsigned char c[4];
	Peer_addr     *node, *p;

	for(i = 0; i < ret - 10; i++) {
		if(memcmp(&buffer[i],"Location: ",10) == 0) { 
			i = i + 10;
			j = 0;
			while(buffer[i]!='?' && iip,"%u.%u.%u.%u",c[0],c[1],c[2],c[3]);//将得到的IP地址存储在Peer_addr结构体中
		i += 4;
		node->port = ntohs(*(unsigned short*)&buffer[i]);//unsigned short为两个字节,所以确定了根据起始地址如何取值
		i += 2;
		node->next = NULL;
	
		// 判断当前peer是否已经存在于链表中
		p = peer_addr_head;
		while(p != NULL) {
			if( memcmp(node->ip,p->ip,strlen(node->ip)) == 0 ) { 
				free(node); 
				break;
			}
			p = p->next;
		}
			
		// 将当前结点添加到链表中
		if(p == NULL) {//此时说明这个peer_addr节点没有在peer_addr_head的链表中,可以添加进去
			if(peer_addr_head == NULL)
				peer_addr_head = node;
			else {
				p = peer_addr_head;
				while(p->next != NULL) p = p->next;
				p->next = node;
			}
		}
	}
		
#ifdef DEBUG
		count = 0;
		p = peer_addr_head;
		while(p != NULL) {
		  printf("+++ connecting peer %-16s:%-5d +++ \n",p->ip,p->port);//调试信息,将链接的peer的地址以及端口信息打印出来
			p = p->next;
			count++;
		}
		printf("peer count is :%d \n",count);
#endif

		return 0;
}


//解析Tracker服务器返回的第二种消息
int parse_tracker_response2(char *buffer,int ret)
{
	int        i, ip_len, port;
	Peer_addr  *node = NULL, *p = peer_addr_head;

	if(peer_addr_head != NULL) {
		printf("Must free peer_addr_head\n");
		return -1;
	}
	
	for(i = 0; i < ret; i++) {
	         if(memcmp(&buffer[i],"2:ip",4) == 0) {//第二种消息的查找更简单,查找ip,查找port,构造peer_addr,然后加入peer_addr_head中就可以了
			i += 4;
			ip_len = 0;
			while(isdigit(buffer[i])) {
				ip_len = ip_len * 10 + (buffer[i] - '0');
				i++;
			}//确定ip地址所占有的字节数
			i++;  // skip ":"
			node = (Peer_addr*)malloc(sizeof(Peer_addr));
			if(node == NULL) { 
				printf("%s:%d error",__FILE__,__LINE__); 
				continue;
			}
			memcpy(node->ip,&buffer[i],ip_len);//因为在第二种方式中,已经包含了格式,所以直接拷贝就可以了,因为为字符串,所以最后家少‘\0’
			(node->ip)[ip_len] = '\0';
			node->next = NULL;
		}
		if(memcmp(&buffer[i],"4:port",6) == 0) {
			i += 6;
			i++;  // skip "i"//这个很关键
			port = 0;
			while(isdigit(buffer[i])) {
				port = port * 10 + (buffer[i] - '0');
				i++;
			}
			if(node != NULL)  node->port = port;//因为i之后就是port,所以直接赋值就可以了
			else continue;
			
			printf("+++ add a peer %-16s:%-5d +++ \n",node->ip,node->port);
			
			if(p == peer_addr_head) { peer_addr_head = node; p = node; }//加入到pee_addr_head中
			else p->next = node;
			node = NULL;
		}
	}
	
	return 0;
}

int add_peer_node_to_peerlist(int *sock,struct sockaddr_in saptr)//这个函数相当与将peer_addr与peer建立了联系
{
	Peer *node;
	
	node = add_peer_node();//在这个地方构建peer_head队列
	if(node == NULL)  return -1;
	
	node->socket = *sock; //相当于对peer的前几个元素进行赋值进行赋值
	node->port   = ntohs(saptr.sin_port);
	node->state  = INITIAL;
	strcpy(node->ip,inet_ntoa(saptr.sin_addr));
	node->start_timestamp = time(NULL);

	return 0;
}

void free_peer_addr_head()
{
	Peer_addr *p = peer_addr_head;
    while(p != NULL) {
		p = p->next;
		free(peer_addr_head);
		peer_addr_head = p;
    }
	peer_addr_head = NULL;
}

你可能感兴趣的:(linux下的C程序开发)