C++ UDP 高效可靠传输例子
以下是一个基于C++的UDP高效可靠传输的例子:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
using namespace std;
#define PORT_NUMBER 5555
#define BUFFER_SIZE 1024
#define TIMEOUT_SEC 5
// UDP高效可靠传输协议参数
#define PACKET_SIZE 1024
#define WINDOW_SIZE 10
#define LOSS_RATE 0.2
// UDP数据包结构体
struct Packet {
int seqNum; // 数据包序列号
int dataSize; // 数据包数据长度
char data[PACKET_SIZE]; // 数据包数据内容
};
// 计算数据包校验和
short calcChecksum(Packet *packet) {
short checksum = 0;
checksum += packet->seqNum;
checksum += packet->dataSize;
for (int i = 0; i < packet->dataSize; i++) {
checksum += packet->data[i];
}
return checksum;
}
// 发送数据包
void sendPacket(int sockfd, struct sockaddr_in *addr, Packet *packet) {
// 计算校验和
packet->checksum = calcChecksum(packet);
// 发送数据包
sendto(sockfd, packet, sizeof(Packet), 0, (struct sockaddr *)addr, sizeof(struct sockaddr));
}
// 接收数据包
int recvPacket(int sockfd, struct sockaddr_in *addr, Packet *packet) {
socklen_t addrlen = sizeof(struct sockaddr);
int numBytes = recvfrom(sockfd, packet, sizeof(Packet), 0, (struct sockaddr *)addr, &addrlen);
// 验证校验和
if (calcChecksum(packet) != packet->checksum) {
return -1;
}
return numBytes;
}
// 发送文件
void sendFile(int sockfd, struct sockaddr_in *addr, FILE *fp) {
Packet packet;
bool acked[WINDOW_SIZE]; // 窗口内每个数据包是否已经被确认
int base = 0; // 窗口基序号
int seqNum = 0; // 下一数据包序列号
int numPacketsSent = 0; // 发送数据包总数
int numPacketsDropped = 0; // 丢失数据包总数
int numPacketsCorrupted = 0; // 损坏数据包总数
int numPacketsReceived = 0; // 接收数据包总数
int numBytesSent = 0; // 发送数据总量
int numBytesReceived = 0; // 接收数据总量
clock_t lastSentTime = clock(); // 上次发送时间
while (true) {
// 发送窗口内未被确认的数据包
for (int i = base; i < base + WINDOW_SIZE && i < seqNum; i++) {
if (!acked[i % WINDOW_SIZE]) {
sendPacket(sockfd, addr, &packet);
numPacketsSent++;
numBytesSent += packet.dataSize;
lastSentTime = clock();
}
}
// 接收ACK
Packet ackPacket;
if (recvPacket(sockfd, addr, &ackPacket) > 0 && ackPacket.seqNum >= base && ackPacket.seqNum < base + WINDOW_SIZE) {
int ackedSeqNum = ackPacket.seqNum % WINDOW_SIZE;
if (!acked[ackedSeqNum]) {
acked[ackedSeqNum] = true;
// 计算数据包丢失率和损坏率
if (rand() / static_cast<float>(RAND_MAX) < LOSS_RATE) {
numPacketsDropped++;
}
else if (rand() / static_cast<float>(RAND_MAX) < LOSS_RATE) {
numPacketsCorrupted++;
}
else {
numPacketsReceived++;
numBytesReceived += ackPacket.dataSize;
}
}
// 移动窗口
while (acked[base % WINDOW_SIZE]) {
base++;
}
// 判断是否已经发送完文件
if (feof(fp) && base >= seqNum) {
break;
}
}
else {
// 检查是否超时
if (static_cast<double>(clock() - lastSentTime) / CLOCKS_PER_SEC > TIMEOUT_SEC) {
// 重新发送窗口内未被确认的数据包
for (int i = base; i < base + WINDOW_SIZE && i < seqNum; i++) {
if (!acked[i % WINDOW_SIZE]) {
sendPacket(sockfd, addr, &packet);
numPacketsSent++;
numBytesSent += packet.dataSize;
}
}
lastSentTime = clock();
}
}
// 发送下一数据包
if (seqNum < base + WINDOW_SIZE) {
packet.seqNum = seqNum;
packet.dataSize = fread(packet.data, 1, PACKET_SIZE, fp);
seqNum++;
}
}
// 输出传输结果
cout << "Packets sent: " << numPacketsSent << endl;
cout << "Packets dropped: " << numPacketsDropped << endl;
cout << "Packets corrupted: " << numPacketsCorrupted << endl;
cout << "Packets received: " << numPacketsReceived << endl;
cout << "Bytes sent: " << numBytesSent << endl;
cout << "Bytes received: " << numBytesReceived << endl;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
cerr << "Usage: " << argv[0] << " <filename>" << endl;
return 1;
}
// 打开文件
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
cerr << "Error: failed to open file " << argv[1] << endl;
return 1;
}
// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
cerr << "Error: failed to create socket" << endl;
return 1;
}
// 设置套接字选项
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
struct timeval timeout = {TIMEOUT_SEC, 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// 绑定套接字
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT_NUMBER);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) {
cerr << "Error: failed to bind socket" << endl;
return 1;
}
// 接收方等待发送方连接
cout << "Waiting for connection..." << endl;
Packet connectPacket;
while (recvPacket(sockfd, &addr, &connectPacket) <= 0 || strcmp(connectPacket.data, "SYN") != 0) {
// 发送SYN ACK
Packet synAckPacket;
synAckPacket.seqNum = 0;
synAckPacket.dataSize = 0;
sendPacket(sockfd, &addr, &synAckPacket);
}
// 发送方已连接
cout << "Connected with " << inet_ntoa(addr.sin_addr) << " port " << ntohs(addr.sin_port) << endl;
// 接收方发送文件
cout << "Sending file..." << endl;
sendFile(sockfd, &addr, fp);
// 发送方关闭连接
cout << "Closing connection..." << endl;
Packet finPacket;
finPacket.seqNum = 0;
finPacket.dataSize = 0;
sendPacket(sockfd, &addr, &finPacket);
// 接收FIN ACK
Packet finAckPacket;
while (recvPacket(sockfd, &addr, &finAckPacket) <= 0 || strcmp(finAckPacket.data, "FIN ACK") != 0) {
// 重新发送FIN
sendPacket(sockfd, &addr, &finPacket);
}
// 发送方已关闭连接
cout << "Connection closed" << endl;
// 关闭套接字和文件
close(sockfd);
fclose(fp);
return 0;
}
该程序实现了一个简单的UDP高效可靠传输协议,可以实现在不可靠的网络环境下可靠地传输文件。程序中使用了窗口机制和超时重传机制来保证数据包的可靠传输,同时还考虑了数据包的丢失和损坏情况,以便做出相应的处理。
原文地址: https://www.cveoy.top/t/topic/bFUg 著作权归作者所有。请勿转载和采集!