BT下载软件剖析
一、软件介绍
1.1 概要介绍
在传统的场景下,当我们想要下载一个文件时,通常是通过HTTP/FTP的方式从服务器上进行下载,这样的缺点是,服务器的带宽有限,当下载的人数过多,服务器的带宽被打满时,可能会导致下载很慢或无法下载。
而BT协议的出现就是为了解决这一问题,BT协议是一种P2P的传输协议,对于大文件、多人同时下载时的效率非常高。
它将文件进行分片,下载的时候一个分片一个分片地下载,同时将自己已经下载的文件分散给其他正在下载的用户,从而将原本服务器的压力分散给了终端用户。用户形成一个图结构,互相点对点地分发文件片段,下载的人越多,下载速度就越快。
本文主要剖析基于BT协议的文件分发系统的各个模块,并提出关于优化的思考。
1.2 系统结构
1.2.1 BT系统包括以下几个实体

1.2.2 客户端下载一个共享文件的流程

1.2.3 系统涉及到的技术

1.2.4 peer消息类型
BT客户端和不同peer之间的通信是通过不同的消息类型来进行的,peer通过识别消息类型,能够识别不同peer的状态,做出不同响应,包括以下消息类型:

1.2.5 系统结构:


二、各模块剖析
2.1 种子解析模块
该模块主要的功能就是获取信息,从Web服务器获取种子文件,解析种子文件的内容,获取共享文件(即待下载文件)的文件名、文件大小,最主要的是获取tracker服务器的地址,同时还会判断要下载的是单文件还是多文件,计算每个piece的hash值,并放到缓存里。
函数 | 功能 |
---|---|
int read_metafile(char *metafile_name) | 该函数的作用是用来解析种子文件,并将种子文件的内容读入缓冲区 |
int find_keyword(char *keyword,long *position) | 从种子文件中查找某个关键字,即通过关键字从种子文件中查找信息。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3mwfM31-1657467498244)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702171033585.png)] |
read_announce_list() | 这里是通过查找关键字来获取Tracker地址,并将获取的地址保存到全局变量announce_list_head指向的链表中。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mUbEO5sG-1657467498245)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702171234210.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-71msupED-1657467498245)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702171307756.png)] |
int add_an_announce(char *url) | peer的URL可能会发生重定向,该部分的功能就是将新的URL记录到Tracker列表中。 |
int is_multi_files() | 判断是下载多个文件还是单文件 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKXCCahv-1657467498245)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702171631131.png)] |
int get_piece_length() | 获取piece的长度 |
get_pieces() | 获取每个piece的hash值,并保存到pieces所指向的缓冲区中 |
get_file_name() | 获取待下载的文件的文件名,如果下载的是多个文件,则获取的是目录名 |
int get_file_length() | 获取待下载文件的长度 |
get_files_length_path() | 对于多文件,获取各个文件的路径以及长度 |
int get_info_hash () | 计算info_hash的值 |
int get_peer_id() | 生成一个惟一的peer id [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nHggZJZT-1657467498245)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702172014807.png)] |
void release_memory_in_parse_metafile() | 释放动态申请的内存 |
int parse_metafile(char *metafile) | 该函数为种子解析模块的入口,调用parse_metafile.c中定义的函数,完成解析种子文件 |
解析种子文件流程:

2.2 位图管理模块
BT协议采用位图指明当前哪些piece已经下载,哪些piece还没有下载,每个piece占一位,值为0表示该piece还未下载到,为1则表明已经下载到该piece。
本模块负责管理位图,客户端与peer建立了连接并进行握手之后,即发送位图给peer告知已下载到哪些piece,同时也接收对方的位图并将其保存在Peer结构体中。每下载到一个piece就更新自己的位图,并发送have消息给所有已建立连接的peer。每当接收到peer发来的have消息就更新该peer的位图。
函数 | 功能 |
---|---|
int create_bitfield() | 在该函数中,创建了待下载文件的位图,用pieces的长度除以20得到总的piece数。![]() [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcfQMnod-1657467498246)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702180238210.png)] |
int get_bit_value(Bitmap *bitmap,int index) | 获取位图中的某一位,用来判断该位对应的piece是否已下载。 |
int set_bit_value(Bitmap *bitmap,int index,unsigned char value) | 设置位图中某一位的值,当piece下载完成后,将对应的位从0改为1 |
int all_zero(Bitmap *bitmap) | 将位图所有位清0 |
int all_set(Bitmap *bitmap) | 将位图所有位放置1 |
void release_memory_in_bitfield() | 释放本模块所申请的动态内存 |
int is_interested(Bitmap *dst,Bitmap *src) | 判断两个peer是否感兴趣,如果peer1拥有某个piece,而peer2没有,则peer2对peer1感兴趣,希望从peer1处下载它没有的piece。 |
int get_download_piece_num() | 获取当前已下载到的总piece数 |
2.3 出错处理模块
该模块主要定义了一些错误类型,在系统发生错误时能够合理处理,防止错误导致程序终止。
bterror.h
#ifndef BTERROR_H
#define BTERROR_H
#define FILE_FD_ERR -1 // 无效的文件描述符
#define FILE_READ_ERR -2 // 读文件失败
#define FILE_WRITE_ERR -3 // 写文件失败
#define INVALID_METAFILE_ERR -4 // 无效的种子文件
#define INVALID_SOCKET_ERR -5 // 无效的套接字
#define INVALID_TRACKER_URL_ERR -6 // 无效的Tracker URL
#define INVALID_TRACKER_REPLY_ERR -7 // 无效的Tracker回应
#define INVALID_HASH_ERR -8 // 无效的hash值
#define INVALID_MESSAGE_ERR -9 // 无效的消息
#define INVALID_PARAMETER_ERR -10 // 无效的函数参数
#define FAILED_ALLOCATE_MEM_ERR -11 // 申请动态内存失败
#define NO_BUFFER_ERR -12 // 没有足够的缓冲区
#define READ_SOCKET_ERR -13 // 读套接字失败
#define WRITE_SOCKET_ERR -14 // 写套接字失败
#define RECEIVE_EXIT_SIGNAL_ERR -15 // 接收到退出程序的信号
2.4 运行日志模块
负责记录程序运行的日志,日志可以用来查询和分析程序行为。
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
2.5 信号处理模块
该模块用来处理SIGINT、SIGTERM等信号,不处理的话会导致程序终止,一些资源未来得及释放。因此为每个信号编写接口函数,收到信号后在函数中进行善后操作。
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
2.6 Peer管理模块
该模块用来管理peer,每个peer会被构造成为一个结构体,然后用链表的形式将每个结构体连接起来,该模块负责对链表结点进行创建,删除等操作。
peer结构体:
typedef struct _Peer {
int socket; // 通过该socket与peer进行通信
char ip[16]; // peer的ip地址
unsigned short port; // peer的端口号
char id[21]; // peer的id
int 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; // next指针
} Peer;
peer一共定义了七种状态,状态信息反映了peer之间的连接是否正常,能否交互数据,是否阻塞等信息,以下是状态转换图:

函数 | 功能 |
---|---|
initialize_peer(Peer *peer) | 初始化Peer结构体 |
Peer* add_peer_node() | 向peer链表添加一个结点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pB9DI0gC-1657467498246)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702225825126.png)] |
int del_peer_node(Peer *peer) | 从peer链表中删除一个结点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEQTxht6-1657467498246)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220702225844638.png)] |
int cancel_request_list(Peer *node) | 撤销当前请求队列 |
int cancel_requested_list(Peer *node) | 撤销当前被请求队列 |
void free_peer_node(Peer *node) | 释放一个peer结点的内存 |
void release_memory_in_peer() | 释放peer管理模块中动态申请的内存 |
2.7 消息处理模块
消息处理模块负责根据当前的状态生成并发送消息,接收以及处理消息。
该模块的函数都是在创建消息,接受并处理消息,就不一一列举,一下只列举几个重要的函数:
函数 | 功能 |
---|---|
int create_handshake_msg(char *info_hash,char *peer_id,Peer *peer) | 用来创建握手消息,并将握手消息存放在发送缓冲区中,可以看到握手消息格式固定,其中包括keyword、hash_info、peer_id等![]() |
int is_complete_message(unsigned char *buff,unsigned int len,int *ok_len) | 判断缓冲区中是否存放了一条完整的消息,会检测握手、chocke、have、bitfield、piece等消息是否完整 |
int process_handshake_msg(Peer *peer,unsigned char *buff,int len) | 处理接收到的一条握手消息,若客户端刚完成初始化,会回复peer握手消息;若已经完成初始化,会将状态改为已握手,同时记录通信时间 |
int parse_response_uncomplete_msg(Peer *p,int ok_len) | 处理收到的消息,此处会只处理缓冲区中完整的信息,处理完之后将缓冲区中不完整的信息移动到缓冲的前面 |
int prepare_send_have_msg() | 主动发送have消息,告知所有的peer自己已拥有哪些piece |
void discard_send_buffer(Peer *peer) | 即将与peer断开时,丢弃发送缓冲区中的消息 |
2.8 缓冲管理模块
网络IO中都设置有缓冲区(因为不能一边进行网络IO交互,一边将数据写入磁盘,CPU对磁盘的读取是比较慢的,所以先把数据缓存在内存里),将下载的数据先保存到缓冲区,缓冲到一定程度后再将数据写入硬盘。
peer请求数据时,先在缓冲区中寻找,若缓冲区中不存在所请求的数据,则读文件并把请求数据所在的piece预先读入到缓冲区中。
缓冲区结构定义:
// 每个Btcache结点维护一个长度为16KB的缓冲区,该缓冲区保存一个slice的数据
typedef struct _Btcache {
unsigned char *buff; // 指向缓冲区的指针
int index; // 数据所在的piece块的索引
int begin; // 数据在piece块中的起始位置
int length; // 数据的长度
unsigned char in_use; // 该缓冲区是否在使用中
unsigned char read_write; // 是发送给peer的数据还是接收到的数据
// 若数据是从硬盘读出,read_write值为0
// 若数据将要写入硬盘,read_write值为1
unsigned char is_full; // 缓冲区是否满
unsigned char is_writed; // 缓冲区中的数据是否已经写入到硬盘中
int access_count; // 对该缓冲区的访问计数
struct _Btcache *next;
} Btcache;
缓冲管理模块负责缓存的创建、将缓存写入磁盘、从磁盘读取数据到缓存等操作,以下是几个比较重要的函数及其功能:
函数 | 功能 |
---|---|
Btcache* initialize_btcache_node() | 创建Btcache结点,分配内存空间并对其成员的值进行初始化 |
int create-btcache() | 创建总大小为16K*1024bit即16MB的缓冲区,缓冲区中的每个Btcache节点也是通过链表来组织的: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZLX59ZY-1657467498246)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703000802198.png)] |
int create_files() | 根据种子文件中的信息创建保存下载数据的文件。通过lseek和write两个函数来实现物理存储空间的分配 |
int write_btcache_node_to_harddisk(Btcache *node) | 将一个batche节点写入磁盘 |
int read_slice_from_harddisk(Btcache *node) | 从硬盘读取数据到缓存 |
int write_btcache_to_harddisk(Peer *peer) | 将一个缓冲区所有batche节点写入磁盘,并释放缓冲区: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WddpZjN6-1657467498246)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703001205237.png)] |
void clear_btcache() | 清空缓冲区 |
2.9 策略管理模块
该部分主要是计算各个peer的下载和上传速度,根据下载速度选择非阻塞peer,采用随机算法选择优化非阻塞peer,以及实现片断选择策略。
该模块就是用来控制peer和peer之间的交互,选择效率最高的”合作者“来进行合作,同时,peer给客户端提供数据,客户端也会给peer提供数据;若peer只会”拿来主义“,那么客户端会使其阻塞。
策略就是每10s计算一下各个peer从该客户端的下载量,除以时间算出下载速度,然后选下载速度最快的四个peer解除阻塞,同时还会维护一个30s的特殊peer,为了使该特殊peer下次能够将客户端解除阻塞,而不至于客户端没有地方下载数据。

函数 | 功能 |
---|---|
int is_in_unchoke_peers(Peer *node) | 判断一个peer是否已经存在于unchoke_peers |
int get_last_index(Peer **array,int len) | 从unchoke_peers中获取下载速度最慢的peer的索引 |
int select_unchoke_peer() | 找出当前下载速度最快的4个peer,将其解除阻塞 |
int get_rand_numbers(int length) | 打乱要下载所有piece,主要为了实现片段选择算法 |
int select_optunchoke_peer() | 从peer队列中随机选择一个peer作为优化非阻塞peer |
int compute_rate() | 计算最近一段时间(如10秒)每个peer的上传下载速度 |
int compute_total_rate() | 计算总的下载和上传速度 |
int is_seed(Peer *node) | 根据位图判断某peer是否为种子 |
2.10 连接Tracker模块
Tracker是指运行于远端服务器上的一个程序。这个程序的功能就是用来追踪到底有多少人在下载同一个文件,当用户连接到这个服务器后,就会获得一个下载者的清单,比如下载者的地址信息等;然后下载软件就会根据这个清单,来自动连上别人的电脑进行文件的下载。
该模块主要负责客户端向Tracker服务器请求获取peers的IP和Port,以及解析返回的数据。
函数 | 功能 |
---|---|
int create_request(…) | 构造发送到Tracker服务器的HTTP GET请求 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qV6YpkOD-1657467498247)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703092550872.png)] |
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的回应消息,获取返回的peers,将每个peer的IP和Port保存到链表中 |
int parse_tracker_response2(char *buffer,int ret) | 解析第二种Tracker的回应消息 |
2.11 与peer交互数据模块
该模块用来管理peer之间的数据交互,包括监听各个socket,建立TCP连接等,关键函数为int download_upload_with_peers()

三、对于优化的思考
3.1 从种子文件种查找某个关键字优化
3.1.1 优化点
在原文中,从种子文件种查找某个关键字是通过int find_keyword(char *keyword,long *position)
这一函数实现的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKYdjiNT-1657467498247)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703105722388.png)]
具体的查找方式是通过C语言库函数memcmp( )来从前往后扫描字符串并一一比对,找到则返回。
翻阅了memcmp( )的源码如下:
int __cdecl memcmp(const void *buf1, const void *buf2, size_t count) {
if (!count) return (0);
while (--count && *(char *) buf1 == *(char *) buf2) {
buf1 = (char *) buf1 + 1;
buf2 = (char *) buf2 + 1;
}
return (*((unsigned char *) buf1) - *((unsigned char *) buf2));
}
在外层通过for循环遍历metafile_content,内层使用while循环来比对每个字节,时间复杂度相当于O(n2),效率是否比较低?
3.1.2 优化方案
将查找方式从memcmp()替换为基于KMP算法的字符串匹配,KMP的时间复杂度为O(m+n),m是文本的长度,n是待匹配字符串的长度,可以大大提高效率。实现方法大致如下:
int strStr(char* haystack, char* needle) {
int n = strlen(haystack), m = strlen(needle);
if (m == 0) {
return 0;
}
int pi[m];
pi[0] = 0;
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = pi[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
pi[i] = j;
}
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = pi[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
3.2 Peer管理模块优化
3.2.1 优化点
系统在管理各个Peer时使用的是链表结构,将每个Peer构造为结构体,使用next指针连接起来形成链表,以下是原文:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hOVqvikj-1657467498247)(/Users/yue/Library/Application Support/typora-user-images/image-20220703111528078.png)]
而在链表中进行添加、删除操作时需要从头到尾遍历,找到对应的位置,时间复杂度O(n),当peer数量较多,遍历可能使得效率较低,以下是添加和删除一个结点的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vizOFNqw-1657467498247)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703111840538.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGVncQHY-1657467498247)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703111858881.png)]
3.2.2 优化方案
将链表管理方式优化为Hash表管理,以peer_id作为key,peer struct作为value,这样在进行添加、删除、查找操作时,时间复杂度为O(1),如要维持链表结构,可以在每个hash node中添加两个前后指针,形成一个有序的hash表,类似于Java uitl包中的LinkedHashMap,这样既可以以链表的形式从头到尾遍历每个peer,又能够在O(1)时间复杂度下进行增删改查,其结构大致如下:

3.3 Peer通信协议优化
3.3.1 优化点
原文中提到,peer之间的通信协议(peer wire protocal)是一个基于TCP协议实现的应用层协议。
而我们知道,TCP是一种面向连接的、可靠的、字节流协议,通过三次握手来建立连接、使用序列号、检验和、应答机制、滑动窗口、超时重传等一系列机制来保证传输数据的可靠性。这一系列机制使得TCP相较于UDP而言,效率更低。
3.3.2 优化方案
将TCP协议改成UDP协议,加快传输效率,UDP不属于连接协议,具有资源消耗少,处理速度快的优点。
但这样做的话,需要考虑如何保证数据传输的可靠性,UDP在传输层是无法保证数据的可靠性的,所以需要放到应用层来保证,在应用层实现保证传输可靠性的机制,使得传输效率尽可能高。
最简单的方式是在应用层模仿传输层TCP的可靠性传输。不考虑拥塞处理,可靠UDP的简单设计。
- 1、添加seq/ack机制,确保数据发送到对端
- 2、添加发送和接收缓冲区,主要是用户超时重传。
- 3、添加超时重传机制。
发送端发送数据时,生成一个随机seq=x,然后每一片按照数据大小分配seq。数据到达接收端后接收端放入缓存,并发送一个ack=x的包,表示对方已经收到了数据。发送端收到了ack包后,删除缓冲区对应的数据。时间到后,定时任务检查是否需要重传数据。
3.4 缓冲区优化
3.4.1 优化点
原文中提到,缓冲管理模块是维护一个大小为16MB的缓冲区,下载时,先把下载的数据保存到缓冲区,达到一定程度后再进行刷盘;同时,其他peer来下载数据时,如果缓冲区没有,当前客户端会先把数据加载到客户端,在传输给peer。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ls0cz4ao-1657467498247)(https://cdn.jsdelivr.net/gh/2714222609/pic-bed/img/image-20220703150614555.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90I60E2q-1657467498247)(/Users/yue/Library/Application Support/typora-user-images/image-20220703150626601.png)]
缓冲区被分为1024个节点,以16个节点为一个整体进行操作。
这样做可以有效起到缓存的作用,提高其它peer从当前客户端下载的速度。
但是原文中好像没有提到有缓存淘汰的机制,考虑以下场景:假设某时间段有四个peer同时从当前客户端下载数据,peer 1想要下载piece1、piece2,客户端从磁盘中将piece1、piece2加载到缓存,peer 1下载完毕。这时缓存满了,因为没有淘汰机制,所以将会调用
int release_read_btcache_node(int base_count)
函数来释放缓存,该函数会随机选择piece进行释放。如果piece1、piece2被释放,当peer2想要获取数据时,又得重新从磁盘中将数据加载到内存。
3.4.2 优化方案
对缓存增加淘汰机制,可采用LRU、LFU等淘汰算法,尽可能的增大缓存复用率。
3.5 Tracker服务器优化
3.5.1 优化点
两个BT用户之间建立初始连接时是靠“tracker服务器”上面的“tracker URL”进行的。Tracker是指运行于远端服务器上的一个程序。这个程序的功能就是用来追踪到底有多少人在下载同一个文件,当用户连接到这个服务器后,就会获得一个下载者的清单,比如下载者的地址信息等;然后下载软件就会根据这个清单,来自动连上别人的电脑进行文件的下载。可以说Tracker服务器是BT下载的核心所在,如果没有此服务,BT下载软件就迷失了方向;
Tracker服务存储着每个peer的IP和端口号,如果Tracker服务器挂掉了,客户端就没有办法获取其他发送者信息。同时,如果有多个tracker服务器,而每个tracker服务器上的数据不互通,客户端可能需要查询多个tracker服务器,才能找到拥有相应资源的服务器。
3.5.2 优化方案
tracker服务器采用分布式存储,保证服务器的高可用性,同时,尽可能使得各个tracker服务器所拥有的资源互通,使得客户端能够快速获取到想要的peer信息。