分类:
2012-02-07 22:13:00
原文地址:连接Tracker模块的设计和实现 作者:liurhyme
连接Tracker模块的主要功能是:构造HTTP请求,请求Tracker服务器发送peer的IP地址和端口号;与Tracker建立连接;解析从Tracker返回的数据。Tracker返回的数据是经过B编码的,解析Tracker的回应和解析种子文件是类似的。本模块由tarcker.h和tracker.c构成。
tracker.h
#ifndef TRACKER_H
#define TRACKER_H
#include
#include "parse_metafile.h"
typedef struct _Peer_addr {
char ip[16];
unsigned short port;
struct _Peer_addr *next;
} Peer_addr;
// 用于将info_hash和peer_id转换为HTTP编码格式
int http_encode(unsigned char *in,int len1,char *out,int len2);
// 从种子文件中存储的Tracker的URL获取Tracker主机名
int get_tracker_name(Announce_list *node,char *name,int len);
// 从种子文件中存储的Tracker的URL获取Tracker端口号
int get_tracker_port(Announce_list *node,unsigned short *port);
// 构造发送到Tracker服务器的HTTP GET请求
int create_request(char *request, int len,Announce_list *node,
unsigned short port,long long down,long long up,
long long left,int numwant);
int prepare_connect_tracker(int *max_sockfd); // 以非阻塞的方式连接Tracker
int prepare_connect_peer(int *max_sockfd); // 以非阻塞的方式连接peer
// 获取Tracker返回的消息类型
int get_response_type(char *buffer,int len,int *total_length);
// 解析第一种Tracker返回的消息
int parse_tracker_response1(char *buffer,int ret,char *redirection,int len);
// 解析第二种Tracker返回的消息
int parse_tracker_response2(char *buffer,int ret);
// 为已建立连接的peer创建peer结点并加入到peer链表中
int add_peer_node_to_peerlist(int *sock,struct sockaddr_in saptr);
// 释放peer_addr指向的链表
void free_peer_addr_head();
#endif
tracker.c文件的头部包括的代码如下。
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]; // 存放infov_hash
extern unsigned char peer_id[20]; // 存放peerv_id
extern Announce_list *announce_list_head; // 存放各个Tracker的URL
extern int *sock; // 连接Tracker的套接字
extern struct sockaddr_in *tracker; // 连接Tracker时使用
extern int *valid; // 指示连接Tracker的状态
extern int tracker_count; // 为Tracker服务器的个数
extern int *peer_sock; // 连接peer的套接字
extern struct sockaddr_in *peer_addr; // 连接peer时使用
extern int *peer_valid; // 指示连接peer的状态
extern int peer_count; // 尝试与多少个peer建立连接
Peer_addr *peer_addr_head = NULL;
tracker.c文件各个函数定义如下。
l int http_encode(unsigned char *in,int len1,char *out,int len2)
功能:进行编码转换,根据HTTP协议,HTTP请求中的非数字和非字母都要进行编码转换。例如,空格符既不属于0~9也不属于a~z、A~Z,需要进行转换,它的ASCII码为0x20,转换为字符串“%20”。本函数较为简单,代码不列出.
l int get_tracker_name(Announce_list *node,char *name,int len)
功能:获取Tracker URL中的主机名部分。例如:是一个Tracker的URL,本函数可获取其主机名为“btfans.3322.org”。
l int get_tracker_port(Announce_list *node,unsigned short *port)
功能:获取Tracker URL中的端口号。例如:是一个Tracker的URL,本函数可获取其端口号即8000。本函数较为简单,。
l int create_request(...)
功能:构造发送到Tracker服务器的HTTP GET请求。
参数:request用于接收生成的请求;len为request所指向的数组的长度;node为指向Tracker的URL;port为监听的端口号;down为已下载的数据量;up为已上传的数据量;left为剩余多少字节未下载;numwant是希望Tracker返回的Peer数,函数实现的代码如下:
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);
http_encode(peer_id,20,encoded_peer_id,100);
srand(time(NULL));
key = rand() / 10000; // 获取一个0~9999之间的随机数
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",
encoded_info_hash,encoded_peer_id,port,up,down,left,
key,numwant,tracker_name);
#ifdef DEBUG
printf("request:%s\n",request);
#endif
return 0;
}
l int get_response_type(char *buffer,int len,int *total_length)
功能:获取Tracker返回的消息的类型。
参数:buffer指向Tracker的回应消息;len为buffer所指向的数组的长度;total_length用于存放Tracker返回数据的长度,函数实现的代码如下:
int get_response_type(char *buffer,int len,int *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;
}
}
// 如果返回的消息不含"5:peers"关键字,则没有返回peer的IP地址及端口号
if(i == len-7) return -1;
// 关键字"5:peers"之后如果是字符‘l’,则说明返回的消息为第一种类型
if(buffer[i] != 'l') return 0;
*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) {
while(isdigit(buffer[i])) {
content_length = content_length * 10 + (buffer[i] - '0');
i++;
}
for(i = 0; i < len-4; i++) {
if(memcmp(&buffer[i],"\r\n\r\n",4) == 0) { i = i+4; break; }
}
if(i != len-4) *total_length = content_length + i;
}
if(*total_length == 0) return -1;
else return 1;
}
程序说明。
(1)第一种类型的Tracker回应为:关键字“5:peers”之后是一个B编码的字符串,该字符串以6个字节为一组,前面4个存放一个peer的IP地址,后面两个存放该peer的端口号。
例如:“d10:done peersi85e8:intervali1800e9:num peersi214e5:peers600:..”。Tracker返回的是一个字典,关键字“10:done peers”对应值为种子数;关键字“8:interval”对应值为Tracker希望多长时间连接一次Tracker,一般为1800秒;关键字“9:num peers”为当前在下载的peer个数;关键字“5:peers”为返回的各个peer的IP地址和端 口号。
(2)第二种类型的Tracker回应为:关键字“5:peers”之后为一个B编码的列表。列表中每个元素的类型都是字典,一个字典用于表示一个peer。
例如:“d10:done peersi2e8:intervali1800e9:num peersi4e5:peersld2:ip11:83.72.54.24 7:peer id 20:
M3-4-2--2cd992318ca4:port=>i6882eed2:ip13:80.15.205.1907:peer id 20:-AZ2104-lN5Svw
K0XgRt4:porti92eee”。
“5:peers”之后为字典的起始符'd',接着依次是2:ip=>11:83.72.54.24,7:peer id=>20:M3-4-2--
2cd992318ca,4:port=>i6882e,然后是字典终止符'e';之后又是一个字典,2:ip=>13:80.15.205.190,
7:peer id=>20:-AZ2104-lN5SvwK0XgRt,4:port=>i92e。
l int prepare_connect_tracker(int *max_sockfd)
功能:以非阻塞的方式连接Tracker,函数实现的代码如下:
int prepare_connect_tracker(int *max_sockfd)
{
int i, flags, ret, count = 0;
struct hostent *ht;
Announce_list *p = announce_list_head;
while(p != NULL) { count++; p = p->next; }
tracker_count = count;
sock = (int *)malloc(count * sizeof(int));
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);
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));
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[i].sin_port = htons(tracker_port);
tracker[i].sin_family = AF_INET;
valid[i] = -1;
}
p = p->next;
}
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(sock[i],F_SETFL,flags|O_NONBLOCK);
// 连接Tracker
ret = connect(sock[i],(struct sockaddr *)&tracker[i],sizeof(struct sockaddr));
if(ret < 0 && errno != EINPROGRESS) valid[i] = 0;
// 如果返回0,说明连接已经建立
if(ret == 0) valid[i] = 1;
}
}
return 0;
OUT:
if(sock != NULL) free(sock);
if(tracker != NULL) free(tracker);
if(valid != NULL) free(valid);
return -1;
}
l int prepare_connect_peer(int *max_sockfd)
功能:以非阻塞的方式连接peer,函数实现的代码如下:
int prepare_connect_peer(int *max_sockfd)
{
int i, flags, ret, count = 0;
Peer_addr *p;
p = peer_addr_head;
while(p != 0) { count++; p = p->next; }
peer_count = count;
peer_sock = (int *)malloc(count*sizeof(int));
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;
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);
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;
}
l int parse_tracker_response1(char *buffer,int ret,char *redirection,int len)
功能:解析第一种Tracker的回应消息(消息格式请参考get_response_type函数的说明部分),函数实现代码如下:
int parse_tracker_response1(char *buffer,int ret,char *redirection,int len)
{
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]!='?' && i
redirection[j] = buffer[i];
i++;
j++;
}
redirection[j] = '\0';
return 1;
}
}
// 获取返回的peer数,关键词"5:peers"之后为各个peer的IP和端口
for(i = 0; i < ret - 7; i++) {
if(memcmp(&buffer[i],"5:peers",7) == 0) { i = i + 7; break; }
}
if(i == ret - 7 ) {
printf("%s:%d can not find keyword 5:peers \n",__FILE__,__LINE__);
return -1;
}
while( isdigit(buffer[i]) ) {
count = count * 10 + (buffer[i] - '0');
i++;
}
i++; // 跳过":"
count = (ret - i) / 6;
// 将每个peer的IP和端口保存到peer_addr_head指向的链表中
for(; count != 0; count--) {
node = (Peer_addr*)malloc(sizeof(Peer_addr));
c[0] = buffer[i]; c[1] = buffer[i+1];
c[2] = buffer[i+2]; c[3] = buffer[i+3];
sprintf(node->ip,"%u.%u.%u.%u",c[0],c[1],c[2],c[3]);
i += 4;
node->port = ntohs(*(unsigned short*)&buffer[i]);
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) {
if(peer_addr_head == NULL)
peer_addr_head = node;
else {
p = peer_addr_head;
while(p->next != NULL) p = p->next;
p->next = node;
}
} // if语句结束
} // for语句结束
return 0;
}
l int parse_tracker_response2(char *buffer,int ret)
功能:解析第二种Tracker的回应消息(消息格式请参考get_response_type函数的说明部分),函数实现的代码如下:
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) {
i += 4;
ip_len = 0;
while(isdigit(buffer[i])) {
ip_len = ip_len * 10 + (buffer[i] - '0');
i++;
}
i++; // 跳过":"
node = (Peer_addr*)malloc(sizeof(Peer_addr));
if(node == NULL) {
printf("%s:%d error",__FILE__,__LINE__);
continue;
}
memcpy(node->ip,&buffer[i],ip_len);
(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;
else continue;
printf("+++ add %-16s:%-5d +++ \n",node->ip,node->port);
if(p == peer_addr_head) { peer_addr_head = node; p = node; }
else p->next = node;
node = NULL;
} // if语句结束
} // for语句结束
return 0;
}
l int add_peer_node_to_peerlist(int *sock,struct sockaddr_in saptr)
功能:为已建立连接的peer创建peer结点并加入到peer链表中,函数实现的代码如下:
int add_peer_node_to_peerlist(int *sock,struct sockaddr_in saptr)
{
Peer *node;
node = add_peer_node();
if(node == NULL) return -1;
node->socket = *sock;
node->port = ntohs(saptr.sin_port);
node->state = INITIAL;
strcpy(node->ip,inet_ntoa(saptr.sin_addr));
node->start_timestamp = time(NULL);
return 0;
}
l void free_peer_addr_head()
功能:释放动态分配的存储空间,函数实现的代码如下:
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;
}