资源下载地址:https://download.csdn.net/download/sheziqiong/85908165
出了新加入的一下文件及其用途。
//队列中的节点
typedef struct node {
void *data;//节点数据
struct node *next;//下一个节点
} node;
//队列,用于存储过程中的各类链表数据
typedef struct queue {
node *head;//队列头部
node *tail;//队列尾部
int n;//队列节点数量
} queue;
用链表实现的队列, 可以储存变长的同一类型数据, 以先进先出的存放和读取数据。项目中大概所以类型存储都用的的队列
1. //数据包头结构
2. typedef struct header_s {
3. short magicnum;//magicnum
4. char version;//版本号
5. char packet_type;//数据包类型
6. short header_len;//包头部长度
7. short packet_len;//包数据长度
8. u_int seq_num;//序列号
9. u_int ack_num;//确认号
10. } header_t;
11. //数据包结构,包括头部与数据
12. typedef struct data_packet {
13. header_t header;//数据包头部
14. char data[DATALEN];//包所包含的数据
15. } data_packet_t;
数据包结构,udp传送过程用到的数据包,包含头部与数据段,头部也是一个结构体,具体结构如上所示。
1. static int up_conn_id = 0;
2. //chunk 结构,用于存储需要下载的 chunk 的状态
3. typedef struct chunk_s {
4. int id;
5. uint8_t sha1[SHA1_HASH_SIZE];//chunk 的哈希值
6. int flag;//是否下载完成
7. int inuse;//是否在下载
8. int num;//该 chunk 可以从多少个 peer 获取
9. char *data;//存储 chunk 的数据
10. queue *peers;//拥有该 chunk 的 peer
11. } chunk_t;
12. //下载连接的结构体
13. typedef struct down_conn_s {
14. uint expect_seq;//下一个需要收到的数据包的序列号
15. chunk_t *chunk;//该连接对应下载 chunk
16. int pos;//当前数据包下载的位置指针
17. struct timeval timer;
18. bt_peer_t *sender;//下载源
19. } down_conn_t;
20. //上传连接的结构体
21. typedef struct up_conn_s {
22. int up_id;//当前上传连接的 id,每一个连接加 1
23. int ssthresh;//当前上传连接的慢启动阈值
24. int is_slow_start;//记录是否是慢启动状态
25. int receive_ack_during_avoid;//记录拥塞避免期间收到的 ack 数量
26. pthread_t pid;//管理这个连接的 time out 的线程 pid
27. int expect_ack;//下一个期待的 ack
28. int last_sent;//上一个发送的包的序列号
29. int available;//可以发送的包的最大序列号
30. int duplicate;//记录收到重复 ack 数量
31. int cwnd;//接收方窗口大小
32. data_packet_t **pkts;//需要上传的数据包数组
33. struct timeval timer;//记录时间
34. bt_peer_t *receiver;//接收数据方
35. } up_conn_t;
36. //下载池,保存了当前的下载连接
37. typedef struct down_pool_s {
38. down_conn_t **conns;//正在使用的下载连接数组
39. int conn;//当前下载池数量
40. int max_conn;//最大下载数量
41. } down_pool_t;
42. //上传池
43. typedef struct up_pool_s {
44. up_conn_t **conns;//正在使用的上传连接数组
45. int conn;//当前上传池数量
46. int max_conn;//最大上传数量
47. } up_pool_t;
48. //当前对等方下载任务
49. typedef struct task_s {
50. int chunk_num;//已经下载的 chunk 数量
51. int need_num;//需要下载的 chunk 数量
52. queue *chunks;//需要下载的 chunk 队列
53. int max_conn;
54. char output_file[BT_FILENAME_LEN];//下载的数据存储到该路径
55. struct timeval timer;
56. } task_t;
每个chunk_t对应着一个chunk,保存当前chunk的状态,是否下载、结束以及下载的数据等等,
每个conn_t都是一个与其他对等方的连接,保存着上传或者下载的信息, pool则保存着所有的conn_t;
task_t是下载任务的维持者,按照get_chunk_file初始化之后,保持着需要下载的任务即chunk,直到下载任务完成后才会释放。
typedef struct time_out_arg {
int seq_num;
int id;
} out_args;
存储线程参数的结构,用于向管理超时线程的函数传递参数。
peer程序启动后,进行一系列初始化操作后,进入peer_run函数,阻塞等待用户的输入或等待其他对等方发送的消息。
接收到消息后有两种情况,来自用户的输入,进行步骤3处理,来自其他对等方的消息,进行步骤4处理
处理用户的输入:调用 bt_parse.c 的函数序列化用户输入,并处理用户的 get指令:根据用户的get_chunk_file生成whohas的数据包,并把whohas 数据包发送给所有对等方,等待其他对等方发送消息。
处理对等方消息:对等方消息由于类型的不同,有不同的处理函数,根据类型到达对应的处理步骤:
whohas类型: 步骤5
ihave类型: 步骤6
get类型: 步骤7
data类型: 步骤8
ack类型: 步骤9
处理whohas类型包:接收到whohas数据包,首先获取该数据包发送方A,并获取数据包中所有的哈希值,然后将哈希值和本地 has_chunk_file 的哈希值进行比较,寻找到所有本地与数据包中相同的哈希值。如果存在相同的值,将这些值封装生成ihava数据包,然后将ihava数据包发还给A。如果不存在相同的值,则不采取操作
处理ihava类型包:在向其他对等方发送whohas包后,其他对等方会发回 ihava包,说明他们所拥有的chunk。接收到ihava包后,处理ihava包的数据,根据ihave包的哈希值在task中为每个chunk保存可用于下载的对等方,然后先挑一个chunk,根据其哈希值创建get类型包,发送给对应的一个对等方,建立连接,进行下载该 chunk,并加入下载池。如果接收到不同对等方发过来的ihava包,可以建立不同连接,同时下载。
处理get类型包:接收到get类型包时,说明该对等方A需要下载一个本地拥有的chunk。获取get包中的哈希值,根据哈希值在master_file中查找其该chunk的id,根据id获取chunk的数据,构建512个data数据包(由于udp包大小有限,每个chunk的大小固定为512k,所以每个构建的udp包包含1k的数据),然后根据当前窗口大小,将数据包分步发送给对等方A,同时启动time out计时器
处理data类型包:data类型包包含所需要的下载数据。如果该包的发送方不是我们所连接的发送方,说明是一个发错的包,不采取操作。如果是连接的发送方,先判断seqnum是不是我们所期望的seqnum,如果是,获取对应的chunk,然后将获取的数据存入chunk的缓存区中,并发送对应的ack包进行确认收到;如果seqnum达到最大,则表示下载完成,结束这个chunk的下载任务。如果不是我们需要的seqnum,则忽视这个包,并且发送一个重复的ack表示没有收到正确的包。
处理ack类型包:一样进行判断是不是与我们连接的对等方,如果不是,忽略。如果是,判断acknum。acknum分为三种情况:
判断acknum是否达到最大,如果是,说明这个chunk以及上传完毕,结束该上传任务,停止计时器
如果acknum大于等于我们所期望的acknum,那么就说明前面的包都已经成功发送(累计确认)。此时还有数据没有上传,则增加对应的变量,并根据当前的状态(慢启动或者拥塞避免)来改变窗口的大小,然后按照当前窗口容量继续发送剩下的数据包,并重启计时器。
如果acknum等于我们所期待的acknum - 1,那么重复记录加一,如果重复记录达到3,说明丢包,马上改变窗口大小为1,改变阈值,进入快速重传,根据窗口重发没有确认的第一个包,重启计时器。
处理结束之后,peer会继续在peer_run函数里阻塞等待其他对等方的消息或者用户的输入。
拥塞控制主要以下列几种情况完成:
为了满足可以同时上传或者下载,采用了上传与下载池的操作,每次接收到上传或者下载请求,会建立对应的上传或者下载链接,每条链接对应一个peer,连接记录与该peer交互的状态,例如上传了多少文件等信息
每次上传或者下载都会从上传池或者下载池中根据peer找到对应的连接,经过该连接来进行操作。由于每个连接互不干扰,所以每次接收到一个包,只需要寻找到对应的连接就可以进行操作,实现了没有采用多线程的并发操作。
实现拥塞避免过程中发现程序无法进入拥塞避免状态经过检查发现,原先采用的拥塞避免的方法是检测上一次改变窗口的时间与这次收到ack的时间相差是否达到一秒。由于1秒时间太长,极易发生重复 ack等问题。后来修改实现,每次发送当前窗口大小的分组数量后,只要这些分组都被确认,就说明经过了一个RTT,此时增加一个窗口大小。
由于超时存在,不知道怎么解决超时问题最后经过思考,采用多线程解决问题,每次重启计时器的时候,就撤销上一个计时器(也就是上一个线程),然后创建新的线程,并sleep 1秒。1秒内如果线程被取消,就说明没有超时;1秒后,线程苏醒,代表超时,执行超时操作即可
图片也放在文档的同级目录下
cp1测试:


cp2测试:

cp3测试:


并发测试

资源下载地址:https://download.csdn.net/download/sheziqiong/85908165