知识点编号:018
难度等级:⭐⭐⭐(掌握)
面试频率:🔥🔥🔥
🎯 一句话总结
UDP本身不可靠,但可以在应用层加上序列号、确认、重传等机制!🛡️
🤔 为什么需要可靠UDP?
矛盾:
- 想要UDP的速度 ⚡
- 又想要TCP的可靠性 ✅
场景:
视频通话、在线游戏
- 需要低延迟(UDP)
- 但完全丢包影响体验
- 需要部分可靠性
解决:
在UDP基础上,应用层实现可靠性机制
= 可靠UDP协议
🔧 可靠性实现方案
方案1:序列号 + 确认应答
基本思路:模拟TCP的确认机制
发送方:
1. 给每个包编号
2. 发送包+序列号
3. 等待ACK
4. 收到ACK,发下一个
接收方:
1. 收到包,检查序列号
2. 发送ACK确认
示例代码:
public class ReliableUDP {
private int seqNum = 0;
public void send(DatagramSocket socket, byte[] data,
InetAddress addr, int port) {
// 添加序列号
ByteBuffer buffer = ByteBuffer.allocate(data.length + 4);
buffer.putInt(seqNum);
buffer.put(data);
DatagramPacket packet = new DatagramPacket(
buffer.array(), buffer.array().length, addr, port
);
// 发送
socket.send(packet);
// 等待ACK(带超时)
socket.setSoTimeout(1000);
byte[] ackBuf = new byte[4];
DatagramPacket ackPacket = new DatagramPacket(ackBuf, ackBuf.length);
try {
socket.receive(ackPacket);
int ackNum = ByteBuffer.wrap(ackBuf).getInt();
if (ackNum == seqNum) {
seqNum++; // ACK正确,序列号+1
}
} catch (SocketTimeoutException e) {
// 超时,重传
send(socket, data, addr, port);
}
}
}
方案2:选择性重传(ARQ)
改进:不是所有包都重传,只重传丢失的包
发送:包1、包2、包3、包4、包5
接收:包1、❌、包3、包4、包5
接收方发送:
ACK1、NAK2(否定确认)、ACK3、ACK4、ACK5
发送方:只重传包2
优点:
- 效率高
- 只重传丢失的包
实现:
需要接收缓冲区
乱序到达的包先缓存
等包2到达后,按序交付
方案3:前向纠错(FEC)
原理:发送冗余数据,可以恢复丢失的包
示例:
发送:包1、包2、包3、包4
同时发送:校验包P(包含1234的校验信息)
如果包2丢失:
收到:包1、❌、包3、包4、包P
利用包P恢复包2!
优点:
- 无需重传
- 延迟低
缺点:
- 增加带宽开销(冗余数据)
- 只能恢复少量丢包
应用:
视频会议、直播
允许少量带宽换取低延迟
方案4:使用成熟协议
KCP协议 🚀
特点:
- 基于UDP的可靠传输协议
- 比TCP快30%-40%
- 牺牲部分带宽换取速度
优势:
✅ 快速重传
✅ 选择性重传
✅ 快速ACK
✅ 流量控制
应用:
- 在线游戏(王者荣耀)
- 实时通信
Java示例(使用KCP-Java):
import io.jpower.kcp.netty.*;
// 创建KCP连接
KcpClient client = new KcpClient();
client.connect("localhost", 10000);
// 发送数据(自动保证可靠性)
client.send("Hello KCP".getBytes());
QUIC协议 ⚡
特点:
- Google开发
- HTTP/3的基础
- 0-RTT连接建立
- 多路复用
优势:
✅ 基于UDP
✅ 可靠传输
✅ 加密(内置TLS)
✅ 连接迁移(IP变化不断开)
应用:
- Chrome浏览器
- YouTube
- Google服务
Java示例(使用QuicHE):
// QUIC客户端
QuicClient client = new QuicClient();
client.connect("quic://example.com:443");
UDT协议 📡
特点:
- UDP-based Data Transfer
- 适合高速网络
- 大数据传输
优势:
✅ 高吞吐量
✅ 拥塞控制
✅ 防火墙友好
应用:
- 数据中心
- CDN
- 科学计算
💻 完整示例代码
/**
* 简单的可靠UDP实现
*/
public class SimpleReliableUDP {
// 发送方
public static class Sender {
private DatagramSocket socket;
private InetAddress address;
private int port;
private int seqNum = 0;
public Sender(String host, int port) throws Exception {
this.socket = new DatagramSocket();
this.address = InetAddress.getByName(host);
this.port = port;
this.socket.setSoTimeout(1000); // 1秒超时
}
public void sendReliable(String message) throws Exception {
byte[] data = message.getBytes();
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
// 构造数据包:[序列号4字节][数据]
ByteBuffer buffer = ByteBuffer.allocate(data.length + 4);
buffer.putInt(seqNum);
buffer.put(data);
// 发送
DatagramPacket packet = new DatagramPacket(
buffer.array(), buffer.array().length,
address, port
);
socket.send(packet);
System.out.println("发送包 #" + seqNum + ": " + message);
// 等待ACK
try {
byte[] ackBuf = new byte[4];
DatagramPacket ackPacket = new DatagramPacket(ackBuf, ackBuf.length);
socket.receive(ackPacket);
int ack = ByteBuffer.wrap(ackBuf).getInt();
if (ack == seqNum) {
System.out.println("收到ACK #" + ack);
seqNum++;
return; // 成功
}
} catch (SocketTimeoutException e) {
System.out.println("超时,重传 #" + seqNum);
}
}
throw new IOException("发送失败,重传" + maxRetries + "次");
}
}
// 接收方
public static class Receiver {
private DatagramSocket socket;
private int expectedSeq = 0;
public Receiver(int port) throws Exception {
this.socket = new DatagramSocket(port);
}
public String receiveReliable() throws Exception {
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
// 解析数据包
ByteBuffer bb = ByteBuffer.wrap(packet.getData(), 0, packet.getLength());
int seqNum = bb.getInt();
byte[] data = new byte[bb.remaining()];
bb.get(data);
String message = new String(data);
System.out.println("收到包 #" + seqNum + ": " + message);
// 发送ACK
ByteBuffer ackBuf = ByteBuffer.allocate(4);
ackBuf.putInt(seqNum);
DatagramPacket ackPacket = new DatagramPacket(
ackBuf.array(), ackBuf.array().length,
packet.getAddress(), packet.getPort()
);
socket.send(ackPacket);
System.out.println("发送ACK #" + seqNum);
expectedSeq++;
return message;
}
}
}
🐛 常见面试题
Q1:如何在UDP基础上实现可靠传输?
答案:
在应用层实现可靠性机制:
1. 序列号
- 给每个包编号
- 检测丢包和乱序
2. 确认应答(ACK)
- 接收方发送ACK确认
- 发送方收到ACK才发下一个
3. 超时重传
- 设置超时时间
- 超时未收到ACK,重传
4. 选择性重传
- 只重传丢失的包
- 提高效率
5. 前向纠错(FEC)
- 发送冗余数据
- 可以恢复丢失的包
6. 使用成熟协议
- KCP:快30%-40%
- QUIC:HTTP/3基础
- UDT:大数据传输
实现选择:
小项目:自己实现(序列号+ACK+重传)
大项目:使用KCP、QUIC等成熟协议
权衡:
- 可靠性 vs 性能
- 延迟 vs 带宽
- 开发成本 vs 效果
🎓 总结
UDP可靠性保证的关键点:
- 序列号:检测丢包和乱序
- 确认应答:ACK机制
- 超时重传:保证到达
- 成熟协议:KCP、QUIC、UDT
记忆口诀:
UDP不可靠 ⚠️
应用层来保障 🛡️
序列号加ACK ✅
KCP、QUIC更强 ⚡
文档创建时间:2025-10-31