- (꒪ꇴ꒪ ),Hello我是祐言QAQ
- 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
- 快上🚘,一起学习,让我们成为一个强大的攻城狮!
- 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
- 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏
ps:首先解释一下为什么没更新了,并不是我不学习了,而是招聘会和面试让我很难有时间坐下来打字,后续还是会更新的。
近日面试中遇到一个问题,面试官问到UDP时问我如何使UDP变得更可靠,我只知道UDP也可以重传,但是具体如何实现呢未曾接触,于是今天分享一下。
想必大家和我一样也背过UDP协议的内容,它是一种面向无连接的协议,它在网络通信中提供了高性能的数据传输,但不保证数据的可靠性。尽管UDP在某些情况下非常有用,但在需要可靠性的场景中,我们可以采用一些策略来增加UDP传输的可靠性。本文将介绍这些策略,包括超时重传、有序接收、应答确认和滑动窗口流量控制。
一、UDP概述
UDP是一种简单的面向数据包的协议,它不提供连接管理、流控制或拥塞控制,因此通常被用于实时通信和多媒体流。但由于UDP不保证数据包的可靠性,它可能在不可靠网络环境下导致数据包丢失或乱序。
二、增加UDP可靠性的策略
1. 超时重传(定时器)
超时重传是一种基本的机制,它通过设置定时器来确保数据包在有限时间内到达接收方。如果定时器超时并且没有接收到应答,发送方将重新发送数据包。
下面是一个简单的C++代码示例:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[1024];
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("创建套接字出错");
exit(1);
}
// 服务器地址配置
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = INADDR_ANY;
socklen_t server_len = sizeof(server_addr);
while (true) {
// 发送数据
const char* data = "Hello, UDP!";
sendto(sockfd, data, strlen(data), 0, (struct sockaddr*)&server_addr, server_len);
// 设置超时
struct timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
// 接收响应
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &server_len);
if (n < 0) {
std::cout << "超时,重新发送数据..." << std::endl;
} else {
buffer[n] = '\0';
std::cout << "从服务器接收响应:" << buffer << std::endl;
}
sleep(1); // 在发送下一个数据包之前等待
}
return 0;
}
2. 有序接收(添加包序号)
为了解决UDP数据包的乱序问题,我们可以为每个数据包添加一个包序号,并在接收端按照序号对数据包进行排序。这有助于确保数据包以正确的顺序到达接收方。
以下是一个示例C++代码:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[1024];
int expected_seq = 0;
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("创建套接字出错");
exit(1);
}
// 服务器地址配置
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = INADDR_ANY;
socklen_t server_len = sizeof(server_addr);
while (true) {
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &server_len);
buffer[n] = '\0';
int seq;
memcpy(&seq, buffer, sizeof(int));
if (seq == expected_seq) {
// 接收到期望的数据包
std::cout << "从服务器接收数据:" << (buffer + sizeof(int)) << std::endl;
expected_seq++;
}
// 发送应答
sendto(sockfd, &seq, sizeof(int), 0, (struct sockaddr*)&server_addr, server_len);
}
return 0;
}
3. 应答确认(Seq/Ack应答机制)
Seq/Ack应答机制允许接收方向发送方发送应答,以确认已成功接收到数据包。如果发送方未收到应答,它可以选择重传数据包。
以下是一个示例C++代码:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet_in.h>
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[1024];
int ack = 0;
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("创建套接字出错");
exit(1);
}
// 服务器地址配置
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = INADDR_ANY;
socklen_t server_len = sizeof(server_addr);
while (true) {
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &server_len);
buffer[n] = '\0';
int seq;
memcpy(&seq, buffer, sizeof(int));
if (seq == ack) {
// 接收到期望的数据包
std::cout << "从服务器接收数据:" << (buffer + sizeof(int)) << std::endl;
ack++;
}
// 发送应答
sendto(sockfd, &ack, sizeof(int), 0, (struct sockaddr*)&server_addr, server_len);
}
return 0;
}
4. 滑动窗口流量控制等机制(滑动窗口协议)
滑动窗口协议允许发送方和接收方之间协商,以控制数据包的流量和顺序。这有助于优化传输的效率和可靠性。
以下是一个示例C++代码:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[1024];
int ack = 0;
int window_size = 5;
int recv_buffer[window_size];
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("创建套接字出错");
exit(1);
}
// 服务器地址配置
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345);
server_addr.sin_addr.s_addr = INADDR_ANY;
socklen_t server_len = sizeof(server_addr);
while (true) {
int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &server_len);
buffer[n] = '\0';
int seq;
memcpy(&seq, buffer, sizeof(int));
if (seq == ack) {
// 接收到期望的数据包
std::cout << "从服务器接收数据:" << (buffer + sizeof(int)) << std::endl;
ack++;
// 检查后续数据包
for (int i = 0; i < window_size; i++) {
int next_seq = ack + i;
if (recv_buffer[next_seq] != 0) {
std::cout << "从服务器接收数据:" << recv_buffer[next_seq] << std::endl;
recv_buffer[next_seq] = 0;
}
}
} else {
// 存储未按顺序到达的数据包
recv_buffer[seq] = (buffer + sizeof(int));
}
// 发送应答
sendto(sockfd, &ack, sizeof(int), 0, (struct sockaddr*)&server_addr, server_len);
}
return 0;
}
三、总结
尽管UDP是一种不提供可靠性传输的协议,但通过实现超时重传、有序接收、应答确认和滑动窗口流量控制等机制,我们可以增加UDP传输的可靠性。这些策略可以根据具体应用的需求来选择和组合,以满足不同的可靠性要求。然而,需要注意的是,这些机制在应用层实现,会引入额外的复杂性和开销,因此对于某些需要高度可靠性的应用,TCP可能仍然是更好的选择。
更多C/C++语言、Linux系统、数据结构和ARM板实战相关文章,关注专栏:
手撕C语言
玩转linux
脚踢数据结构
系统、网络编程
探索C++
6818(ARM)开发板实战
📢写在最后
- 今天的分享就到这啦~
- 觉得博主写的还不错的烦劳
一键三连喔
~ - 🎉🎉🎉感谢关注🎉🎉🎉