协议设计思路
- 拥塞控制:
- 采用类似TCP的拥塞控制机制,如慢启动、拥塞避免、快速重传和快速恢复。
- 维护一个拥塞窗口(cwnd),初始值可以设置为一个较小的值(如1个最大段大小MSS)。
- 慢启动阶段,每收到一个ACK,cwnd增加一个MSS;当cwnd达到慢启动门限(ssthresh)时,进入拥塞避免阶段,每收到cwnd个ACK,cwnd增加一个MSS。
- 当检测到丢包(超时或3个冗余ACK),调整ssthresh和cwnd,进入快速重传或快速恢复阶段。
- 丢包重传:
- 为每个发送的数据包分配一个唯一的序列号。
- 发送方维护一个重传定时器,当定时器超时且对应的ACK未收到时,重传该数据包。
- 接收方通过序列号来判断数据包是否重复,丢弃重复的数据包。
- 抖动缓冲:
- 接收方维护一个缓冲区,用于存储接收到但顺序不正确的数据包。
- 根据序列号对数据包进行排序,按正确顺序交付给应用层。
- 为了避免缓冲区无限增长,设置一个合理的缓冲区大小,当缓冲区满且有新的数据包到达时,丢弃最早到达的数据包。
核心功能代码框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <pthread.h>
#define PORT 8080
#define MAX_SEQ 65535
#define MSS 1024
#define TIMEOUT 2 // 重传超时时间(秒)
// 数据包结构
typedef struct {
unsigned short seq_num;
unsigned short ack_num;
unsigned short length;
char data[MSS];
} Packet;
// 发送方状态
typedef struct {
int sockfd;
struct sockaddr_in servaddr;
unsigned short cwnd;
unsigned short ssthresh;
unsigned short next_seq_num;
unsigned short expected_ack_num;
Packet *send_buffer[MAX_SEQ];
int buffer_head;
int buffer_tail;
int timer_running;
pthread_t timer_thread;
} Sender;
// 接收方状态
typedef struct {
int sockfd;
struct sockaddr_in cliaddr;
unsigned short expected_seq_num;
Packet *recv_buffer[MAX_SEQ];
int buffer_head;
int buffer_tail;
} Receiver;
// 初始化发送方
void init_sender(Sender *sender, const char *ip) {
sender->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sender->sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&sender->servaddr, 0, sizeof(sender->servaddr));
memset(&sender->send_buffer, 0, sizeof(sender->send_buffer));
sender->servaddr.sin_family = AF_INET;
sender->servaddr.sin_port = htons(PORT);
sender->servaddr.sin_addr.s_addr = inet_addr(ip);
sender->cwnd = 1 * MSS;
sender->ssthresh = 65535;
sender->next_seq_num = 0;
sender->expected_ack_num = 0;
sender->buffer_head = 0;
sender->buffer_tail = 0;
sender->timer_running = 0;
}
// 初始化接收方
void init_receiver(Receiver *receiver) {
receiver->sockfd = socket(AF_INET, SOCK_DUDP, 0);
if (receiver->sockfd < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}
memset(&receiver->cliaddr, 0, sizeof(receiver->cliaddr));
memset(&receiver->recv_buffer, 0, sizeof(receiver->recv_buffer));
receiver->cliaddr.sin_family = AF_INET;
receiver->cliaddr.sin_port = htons(PORT);
receiver->cliaddr.sin_addr.s_addr = INADDR_ANY;
if (bind(receiver->sockfd, (const struct sockaddr *)&receiver->cliaddr, sizeof(receiver->cliaddr)) < 0) {
perror("bind failed");
close(receiver->sockfd);
exit(EXIT_FAILURE);
}
receiver->expected_seq_num = 0;
receiver->buffer_head = 0;
receiver->buffer_tail = 0;
}
// 发送数据包
void send_packet(Sender *sender, Packet *packet) {
sendto(sender->sockfd, (const Packet *)packet, sizeof(Packet), MSG_CONFIRM, (const struct sockaddr *)&sender->servaddr, sizeof(sender->servaddr));
sender->send_buffer[sender->next_seq_num] = packet;
sender->next_seq_num = (sender->next_seq_num + 1) % MAX_SEQ;
if (!sender->timer_running) {
// 启动重传定时器
}
}
// 接收数据包
void receive_packet(Receiver *receiver) {
Packet packet;
socklen_t len = sizeof(receiver->cliaddr);
recvfrom(receiver->sockfd, (Packet *)&packet, sizeof(Packet), MSG_WAITALL, (struct sockaddr *)&receiver->cliaddr, &len);
if (packet.seq_num == receiver->expected_seq_num) {
// 按序交付给应用层
receiver->expected_seq_num = (receiver->expected_seq_num + 1) % MAX_SEQ;
// 从缓冲区中查找并交付后续按序的数据包
} else {
// 存入缓冲区
}
// 发送ACK
}
// 重传定时器处理函数
void *timer_handler(void *arg) {
Sender *sender = (Sender *)arg;
while (sender->timer_running) {
sleep(TIMEOUT);
if (sender->timer_running) {
// 重传未确认的数据包
// 调整拥塞控制参数
}
}
return NULL;
}
关键函数详细说明
- init_sender(Sender *sender, const char *ip):
- 功能:初始化发送方的套接字、服务器地址以及拥塞控制和发送状态相关的变量。
- 参数:
sender
:指向发送方状态结构体的指针。
ip
:服务器的IP地址。
- init_receiver(Receiver *receiver):
- 功能:初始化接收方的套接字、绑定地址以及接收状态相关的变量。
- 参数:
- send_packet(Sender *sender, Packet *packet):
- 功能:将数据包发送到服务器,并更新发送方的状态,如将数据包存入发送缓冲区,更新序列号,并在必要时启动重传定时器。
- 参数:
sender
:指向发送方状态结构体的指针。
packet
:指向要发送的数据包的指针。
- receive_packet(Receiver *receiver):
- 功能:从套接字接收数据包,根据序列号进行处理,按序交付给应用层或存入缓冲区,并发送ACK。
- 参数:
- timer_handler(void *arg):
- 功能:作为重传定时器的线程函数,定时检查是否有未确认的数据包需要重传,并调整拥塞控制参数。
- 参数: