• C语言实现计算机网络技术


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

    说明文档结构:

    1. 代码结构
    2. 数据结构
    3. 程序逻辑
    4. 关于超时与拥塞控制
    5. 关于并发运行
    6. 关于开发过程中的问题
    7. 程序运行结果图

    一、代码结构

    1. peer.c:程序的主结构,主函数入口,实现了对用户输入命令的处理和以及对接收到的不同类型数据包处理
    2. queue.c/h:队列数据结构的封装文件并实现了队列的一系列操作的函数
    3. task.c/h:上传和下载任务的函数分装文件,定义了任务、连接等结构,实现了对上传和下载的控制操作函数
    4. timer.c/h:计算时间辅助文件,实现了计算两个时间的差、设定时间等函数
    5. transfer.c/h:传送和文件处理的函数封装文件,实现了传送过程中对接收数据、文件的处理函数,以及发送数据包函数
    6. udpPacket.c/h:数据包类型的定义封装文件,实现了不同类型数据包的生成函数以及网络、本地类型转换的辅助函数对于project给的文件,除了peer.c文件,其他没有进行修改,所以只列

    出了新加入的一下文件及其用途。

    二、数据结构

    queue.h:

    //队列中的节点
     typedef struct node {
     	void *data;//节点数据
     	struct node *next;//下一个节点
     } node;
     //队列,用于存储过程中的各类链表数据
     typedef struct queue {
     	node *head;//队列头部
     	node *tail;//队列尾部
     	int n;//队列节点数量
     } queue;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    用链表实现的队列, 可以储存变长的同一类型数据, 以先进先出的存放和读取数据。项目中大概所以类型存储都用的的队列

    udpPacket.h:

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    数据包结构,udp传送过程用到的数据包,包含头部与数据段,头部也是一个结构体,具体结构如上所示。

    task.h:

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    每个chunk_t对应着一个chunk,保存当前chunk的状态,是否下载、结束以及下载的数据等等,

    每个conn_t都是一个与其他对等方的连接,保存着上传或者下载的信息, pool则保存着所有的conn_t;

    task_t是下载任务的维持者,按照get_chunk_file初始化之后,保持着需要下载的任务即chunk,直到下载任务完成后才会释放。

    peer.c

    typedef struct time_out_arg {
        int seq_num;
        int id;
    } out_args;
    
    • 1
    • 2
    • 3
    • 4

    存储线程参数的结构,用于向管理超时线程的函数传递参数。

    三、程序逻辑

    1. peer程序启动后,进行一系列初始化操作后,进入peer_run函数,阻塞等待用户的输入或等待其他对等方发送的消息。

    2. 接收到消息后有两种情况,来自用户的输入,进行步骤3处理,来自其他对等方的消息,进行步骤4处理

    3. 处理用户的输入:调用 bt_parse.c 的函数序列化用户输入,并处理用户的 get指令:根据用户的get_chunk_file生成whohas的数据包,并把whohas 数据包发送给所有对等方,等待其他对等方发送消息。

    4. 处理对等方消息:对等方消息由于类型的不同,有不同的处理函数,根据类型到达对应的处理步骤:

      whohas类型: 步骤5

      ihave类型: 步骤6

      get类型: 步骤7

      data类型: 步骤8

      ack类型: 步骤9

    5. 处理whohas类型包:接收到whohas数据包,首先获取该数据包发送方A,并获取数据包中所有的哈希值,然后将哈希值和本地 has_chunk_file 的哈希值进行比较,寻找到所有本地与数据包中相同的哈希值。如果存在相同的值,将这些值封装生成ihava数据包,然后将ihava数据包发还给A。如果不存在相同的值,则不采取操作

    6. 处理ihava类型包:在向其他对等方发送whohas包后,其他对等方会发回 ihava包,说明他们所拥有的chunk。接收到ihava包后,处理ihava包的数据,根据ihave包的哈希值在task中为每个chunk保存可用于下载的对等方,然后先挑一个chunk,根据其哈希值创建get类型包,发送给对应的一个对等方,建立连接,进行下载该 chunk,并加入下载池。如果接收到不同对等方发过来的ihava包,可以建立不同连接,同时下载。

    7. 处理get类型包:接收到get类型包时,说明该对等方A需要下载一个本地拥有的chunk。获取get包中的哈希值,根据哈希值在master_file中查找其该chunk的id,根据id获取chunk的数据,构建512个data数据包(由于udp包大小有限,每个chunk的大小固定为512k,所以每个构建的udp包包含1k的数据),然后根据当前窗口大小,将数据包分步发送给对等方A,同时启动time out计时器

    8. 处理data类型包:data类型包包含所需要的下载数据。如果该包的发送方不是我们所连接的发送方,说明是一个发错的包,不采取操作。如果是连接的发送方,先判断seqnum是不是我们所期望的seqnum,如果是,获取对应的chunk,然后将获取的数据存入chunk的缓存区中,并发送对应的ack包进行确认收到;如果seqnum达到最大,则表示下载完成,结束这个chunk的下载任务。如果不是我们需要的seqnum,则忽视这个包,并且发送一个重复的ack表示没有收到正确的包。

    9. 处理ack类型包:一样进行判断是不是与我们连接的对等方,如果不是,忽略。如果是,判断acknum。acknum分为三种情况:

      • 判断acknum是否达到最大,如果是,说明这个chunk以及上传完毕,结束该上传任务,停止计时器

      • 如果acknum大于等于我们所期望的acknum,那么就说明前面的包都已经成功发送(累计确认)。此时还有数据没有上传,则增加对应的变量,并根据当前的状态(慢启动或者拥塞避免)来改变窗口的大小,然后按照当前窗口容量继续发送剩下的数据包,并重启计时器。

      • 如果acknum等于我们所期待的acknum - 1,那么重复记录加一,如果重复记录达到3,说明丢包,马上改变窗口大小为1,改变阈值,进入快速重传,根据窗口重发没有确认的第一个包,重启计时器。

    10. 处理结束之后,peer会继续在peer_run函数里阻塞等待其他对等方的消息或者用户的输入。

    四、关于超时与拥塞控制

    拥塞控制主要以下列几种情况完成:

    1. 慢启动状态:初始窗口为1,慢启动状态下,每次收到一个ack,窗口大小增加1,当达到阈值之后,进入拥塞避免状态
    2. 拥塞避免状态:窗口大小达到阈值之后的状态,此时不再快速增加窗口大小,而是每次在收到ack后累加收到ack与期望的ack差值,当累加值大于等于当前窗口大小时,说明至少经过一个RTT,则窗口加1
    3. 快速重传:将阈值设置为当前窗口大小的一半(至少为2),并且将窗口大小改为1,然后按照窗口大小重发未被确认的包,恢复慢启动状态。在收到重复 ack 3次或者超时后,都会进行快速重传,并重启计时器
    4. 重复ack:收到的ack等于期望的ack-1的时候代表重复ack,当连续3次收到重复ack时,则表示可能丢包了,进行快速重传
    5. 超时:每次收到成功的ack后或者快速重传后,会重启计时器,开启计时线程,计时线程会sleep 1秒,期间可以被取消,如果计时线程没有被取消,那么就表示超时,超时马上进行快速重传。

    五、关于并发运行

    为了满足可以同时上传或者下载,采用了上传与下载池的操作,每次接收到上传或者下载请求,会建立对应的上传或者下载链接,每条链接对应一个peer,连接记录与该peer交互的状态,例如上传了多少文件等信息

    每次上传或者下载都会从上传池或者下载池中根据peer找到对应的连接,经过该连接来进行操作。由于每个连接互不干扰,所以每次接收到一个包,只需要寻找到对应的连接就可以进行操作,实现了没有采用多线程的并发操作。

    六、开发过程遇到的问题

    1. 实现拥塞避免过程中发现程序无法进入拥塞避免状态经过检查发现,原先采用的拥塞避免的方法是检测上一次改变窗口的时间与这次收到ack的时间相差是否达到一秒。由于1秒时间太长,极易发生重复 ack等问题。后来修改实现,每次发送当前窗口大小的分组数量后,只要这些分组都被确认,就说明经过了一个RTT,此时增加一个窗口大小。

    2. 由于超时存在,不知道怎么解决超时问题最后经过思考,采用多线程解决问题,每次重启计时器的时候,就撤销上一个计时器(也就是上一个线程),然后创建新的线程,并sleep 1秒。1秒内如果线程被取消,就说明没有超时;1秒后,线程苏醒,代表超时,执行超时操作即可

    七、程序运行结果图:

    图片也放在文档的同级目录下

    cp1测试:

    在这里插入图片描述
    在这里插入图片描述

    cp2测试:

    在这里插入图片描述

    cp3测试:

    在这里插入图片描述
    在这里插入图片描述

    并发测试

    在这里插入图片描述
    资源下载地址:https://download.csdn.net/download/sheziqiong/85908165

  • 相关阅读:
    怎么调监控清晰度,监控画面不清晰怎么修复?
    进销存仓库管理系统的优势(一)
    服务器数据恢复-服务器系统损坏启动蓝屏的数据恢复案例
    惠普打印机驱动安装
    【带头学C++】----- 七、链表 ---- 7.5 学生管理系统(链表--上)
    C++的线程介绍
    ImmunoChemistry艾美捷高级钙素AM细胞活力试剂盒方案
    自动化立体仓库控制系统-过程通信处理
    CV第四次上机 利用双目图像计算深度图
    JDK8到JDK17新特性
  • 原文地址:https://blog.csdn.net/newlw/article/details/125617554