连接Tracker模块的设计和实现

3113阅读 1评论2012-02-07 liurhyme
分类:LINUX

                 连接Tracker模块的设计和实现

连接Tracker模块的主要功能是:构造HTTP请求,请求Tracker服务器发送peerIP地址和端口号;与Tracker建立连接;解析从Tracker返回的数据。Tracker返回的数据是经过B编码的,解析Tracker的回应和解析种子文件是类似的。本模块由tarcker.htracker.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_hashpeer_id转换为HTTP编码格式

int http_encode(unsigned char *in,int len1,char *out,int len2);

// 从种子文件中存储的TrackerURL获取Tracker主机名

int get_tracker_name(Announce_list *node,char *name,int len);

// 从种子文件中存储的TrackerURL获取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;     // 存放各个TrackerURL

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请求中的非数字和非字母都要进行编码转换。例如,空格符既不属于09也不属于azA~Z,需要进行转换,它的ASCII码为0x20转换为字符串“%20”。本函数较为简单,代码不列出.

l int get_tracker_name(Announce_list *node,char *name,int len)

功能:获取Tracker URL中的主机名部分。例如:是一个TrackerURL,本函数可获取其主机名为“btfans.3322.org”。

l int get_tracker_port(Announce_list *node,unsigned short *port)

功能:获取Tracker URL中的端口号。例如:是一个TrackerURL,本函数可获取其端口号即8000。本函数较为简单,。

l int create_request(...)

功能:构造发送到Tracker服务器的HTTP GET请求。

参数:request用于接收生成的请求;lenrequest所指向的数组的长度;node为指向TrackerURLport为监听的端口号;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;    // 获取一个09999之间的随机数

    

    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的回应消息;lenbuffer所指向的数组的长度;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"关键字,则没有返回peerIP地址及端口号

    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个存放一个peerIP地址,后两个存放该peer的端口号。

如:d10:done peersi85e8:intervali1800e9:num peersi214e5:peers600:..Tracker返回的是一个字典,关键字10:done peers对应值为种子数;关键字8:interval”对应值为Tracker希望多长时间连接一次Tracker,一般为1800秒;关键字9:num peers为当前在下载的peer个数;关键字5:peers为返回的各个peerIP地址和端        口号。

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.247:peer id=>20:M3-4-2--
2cd992318ca4:port=>i6882e,然后是字典终止符'e';之后又是一个字典,2:ip=>13:80.15.205.190
7:peer id=>20:-AZ2104-lN5SvwK0XgRt4: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"之后为各个peerIP和端口

    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;

    // 将每个peerIP和端口保存到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;

}

上一篇:hypercall的实现机制
下一篇:iptable防火墙详解

文章评论