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 checksum; // 校验和
};
// 计算数据包校验和
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 高效可靠传输协议,可以实现在不可靠的网络环境下可靠地传输文件。程序中使用了窗口机制和超时重传机制来保证数据包的可靠传输,同时还考虑了数据包的丢失和损坏情况,以便做出相应的处理。
程序特点:
- 窗口机制: 窗口机制允许发送方在收到确认之前发送多个数据包,提高了传输效率。
- 超时重传: 超时重传机制确保了数据包的可靠传输,如果数据包在规定的时间内没有收到确认,则会重新发送。
- 数据包校验和: 使用校验和来检测数据包在传输过程中是否被损坏。
- 连接建立和关闭: 程序使用三路握手协议建立连接,并使用四路挥手协议关闭连接。
使用方法:
- 编译程序:
g++ udp_reliable.cpp -o udp_reliable - 运行程序:
./udp_reliable <filename>,其中<filename>是要发送的文件名。
注意:
- 该程序仅供参考,实际应用中可能需要根据具体情况进行修改。
- 程序中使用了
rand()函数生成随机数,模拟数据包丢失和损坏。 - 程序中假设接收方和发送方在同一个网络中,并且能够互相访问。
其他说明:
LOSS_RATE变量用于控制数据包丢失和损坏的概率。WINDOW_SIZE变量用于设置窗口大小。TIMEOUT_SEC变量用于设置超时时间。
总结:
该程序提供了一个简单的 UDP 高效可靠文件传输协议的实现示例,可以作为开发可靠网络应用程序的参考。
原文地址: https://www.cveoy.top/t/topic/m9Vs 著作权归作者所有。请勿转载和采集!