当前位置: 首页 > news >正文

Unity中可靠的UDP实现

可靠 UDP(Reliable UDP)是一种在用户数据报协议(UDP)基础上,通过添加额外机制来实现可靠数据传输的技术。与传统 UDP 相比,它克服了 UDP 本身不保证数据可靠性、顺序性以及可能丢失数据的缺点,同时保留了 UDP 在某些场景下(如实时性要求高)相对于 TCP 的优势,如低延迟和较少的系统开销。

1. 为什么需要可靠 UDP

实时性应用需求:在一些实时性要求极高的场景,如在线游戏、实时视频流、音频流传输等,TCP 的拥塞控制和重传机制可能会导致较大的延迟,无法满足实时交互的需求。而 UDP 虽然能快速传输数据,但不能保证数据的可靠到达。可靠 UDP 则结合了两者的优点,在保证实时性的同时,尽量确保数据的可靠传输。
特定网络环境适应性:在某些网络环境中,如无线网络、卫星网络等,网络状况可能不稳定,丢包现象较为常见。可靠 UDP 能够通过自身的重传等机制,在这类网络环境下维持相对稳定的数据传输。

2.可靠 UDP 与 TCP 的比较

延迟:
可靠 UDP:由于采用了更灵活的重传机制和较小的头部开销(相较于 TCP),在网络状况良好时,延迟通常比 TCP 低,更适合实时性要求高的应用。
TCP:为了保证数据的可靠传输和顺序性,TCP 在传输过程中需要进行复杂的拥塞控制和流量控制,这可能导致较高的延迟,尤其是在网络拥塞时。
可靠性:
可靠 UDP:通过序列号、重传、去重等机制,在应用层实现了数据的可靠传输,但其可靠性依赖于具体的实现和网络环境。
TCP:在传输层提供了可靠的字节流服务,确保数据无差错、按顺序到达,可靠性更高。
资源消耗:
可靠 UDP:头部开销较小,不需要像 TCP 那样维护复杂的连接状态,因此在系统资源消耗方面相对较低,适合在资源受限的设备上使用。
TCP:需要维护连接状态、进行拥塞控制等,占用较多的系统资源,如内存和 CPU。

3. 应用场景

游戏中的实时操作指令(如玩家移动、技能释放等)对实时性要求极高,同时也需要保证一定的可靠性。可靠 UDP 可以在低延迟的情况下,尽量确保这些指令准确无误地传输到服务器或其他玩家客户端。

5.可靠 UDP 的关键机制实现

序列号管理:
原理:为每个发送的数据包分配一个唯一的序列号。发送方按顺序递增序列号,接收方通过序列号来判断数据包的顺序,从而对乱序到达的数据包进行排序,同时也能识别重复的数据包。
作用:确保接收方接收到的数据顺序与发送方一致,避免因数据包乱序导致的数据处理错误。同时,通过序列号可以实现去重功能,防止重复处理相同的数据。

代码实现:定义一个序列号,在发送方发送消息时递增,将序列号放到网络消息包的头部

 // 序列号private int nextSequenceNumber = 0;//序列号放入网络消息包int sequenceNumber = nextSequenceNumber++;byte[] sequenceBytes = BitConverter.GetBytes(sequenceNumber);byte[] combinedData = new byte[sequenceBytes.Length + data.Length];Buffer.BlockCopy(sequenceBytes, 0, combinedData, 0, sequenceBytes.Length);Buffer.BlockCopy(data, 0, combinedData, sequenceBytes.Length, data.Length);

重传机制:
原理:发送方在发送数据包后,启动一个定时器。如果在设定的超时时间内没有收到接收方对该数据包的确认(ACK),则认为数据包丢失,重新发送该数据包。
作用:弥补 UDP 本身不保证数据可靠传输的缺陷,确保即使数据包在网络中丢失,也能最终被接收方正确接收。

代码实现:

 // 存储已发送但未确认的数据包private Dictionary<int, Tuple<byte[], Stopwatch>> unacknowledgedPackets = new Dictionary<int, Tuple<byte[], Stopwatch>>();// 存储已接收的数据包序列号,用于去重private HashSet<int> receivedSequenceNumbers = new HashSet<int>();// 当前窗口内已发送的数据包数量private int currentWindowCount = 0;// 基础超时时间(毫秒)private int baseTimeout = 500;// 超时时间调整因子private float timeoutAdjustFactor = 1.5f;// 定时检查未确认的数据包,进行重传public void Update(){List<int> keysToRemove = new List<int>();foreach (var kvp in unacknowledgedPackets){if (kvp.Value.Item2.ElapsedMilliseconds > baseTimeout){// 重传byte[] dataToResend = kvp.Value.Item1;// SendData(dataToResend); // 调整超时时间baseTimeout = (int)(baseTimeout * timeoutAdjustFactor);kvp.Value.Item2.Restart();}}foreach (int key in keysToRemove){unacknowledgedPackets.Remove(key);currentWindowCount--;}}

去重处理:
原理:接收方维护一个已接收序列号的集合。当接收到一个新数据包时,首先检查其序列号是否在该集合中。如果存在,则说明该数据包是重复的,直接丢弃;否则,将序列号加入集合,并处理数据包。
作用:避免接收方对重复的数据进行多次处理,防止数据处理错误和资源浪费。

代码实现:

   // 存储已接收的数据包序列号,用于去重private HashSet<int> receivedSequenceNumbers = new HashSet<int>();   int sequenceNumber = BitConverter.ToInt32(data, 0);if (receivedSequenceNumbers.Contains(sequenceNumber)){return null; // 重复数据包,丢弃}receivedSequenceNumbers.Add(sequenceNumber);byte[] actualData = new byte[data.Length - sizeof(int)];Buffer.BlockCopy(data, sizeof(int), actualData, 0, actualData.Length);

滑动窗口机制:
原理:发送方维护一个滑动窗口,窗口内包含可以连续发送的数据包。窗口大小决定了在未收到 ACK 的情况下,发送方可以发送的最大数据包数量。当发送方收到某个已发送数据包的 ACK 时,窗口向前滑动,允许发送新的数据包。
作用:提高数据传输效率,在保证可靠性的前提下,充分利用网络带宽。通过控制窗口大小,还可以在一定程度上避免网络拥塞。

代码实现:

    // 滑动窗口大小private int windowSize = 10;// 当前窗口内已发送的数据包数量private int currentWindowCount = 0; // 检查滑动窗口while (currentWindowCount >= windowSize){Thread.Sleep(10); // 等待窗口有空闲位置}currentWindowCount++;

确认机制(ACK):
原理:接收方在正确接收到数据包后,向发送方发送一个确认消息(ACK),其中包含已接收数据包的序列号。发送方根据接收到的 ACK,确认数据包已被成功接收,并从待重传队列中移除相应数据包。
作用:让发送方了解数据包的接收情况,是重传机制和滑动窗口机制正常运行的基础。

代码:

 // 发送ACKpublic byte[] GenerateAck(int sequenceNumber){return BitConverter.GetBytes(sequenceNumber);}// 处理接收到的ACKpublic void ProcessAck(byte[] ackData){int sequenceNumber = BitConverter.ToInt32(ackData, 0);if (unacknowledgedPackets.ContainsKey(sequenceNumber)){unacknowledgedPackets.Remove(sequenceNumber);currentWindowCount--;}}

6.测试

测试代码:

结果:

其他有用链接:

Reliable Data Transfer over UDP (youtube.com)

詳解 Reliable UDP (youtube.com)

http://www.lryc.cn/news/538505.html

相关文章:

  • CentOS 7操作系统部署KVM软件和创建虚拟机
  • Golang GORM系列:GORM分页和排序
  • WPF的MVVMLight框架
  • 微服务SpringCloudAlibaba组件sentinel教程【详解sentinel的使用以及流量控制、熔断降级、热点参数限流等,附有示例+代码】
  • ScoreFlow:通过基于分数的偏好优化掌握 LLM 智体工作流程
  • 数字水印嵌入及提取系统——基于小波变换GUI
  • 基于海思soc的智能产品开发(图像处理的几种需求)
  • 【R语言】聚类分析
  • Spring 项目接入 DeepSeek,分享两种超简单的方式!
  • docker 进阶命令(基于Ubuntu)
  • 机器学习数学基础:29.t检验
  • HarmonyNext上传用户相册图片到服务器
  • WebAssembly 3.0发布:浏览器端高性能计算迎来新突破!
  • 计算机组成原理—— 外围设备(十三)
  • 面试题之Vuex,sessionStorage,localStorage的区别
  • window中git bash使用conda命令
  • 象棋掉落动画(局部旋转动画技巧)
  • Pycharm 2024在解释器提供的python控制台中运行py文件
  • 课题推荐:高空长航无人机多源信息高精度融合导航技术研究
  • 《DeepSeek训练算法:开启高效学习的新大门》
  • promise用法总结以及手写promise
  • 春招项目=图床+ k8s 控制台(唬人专用)
  • Android 11.0 系统settings添加ab分区ota升级功能实现二
  • 【Spring+MyBatis】_图书管理系统(上篇)
  • 什么是3D视觉无序抓取?
  • 【Java】理解字符串拼接与数值运算的优先级
  • [250217] x-cmd 发布 v0.5.3:新增 DeepSeek AI 模型支持及飞书/钉钉群机器人 Webhook 管理
  • 渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)
  • Linux、Docker与Redis核心知识点与常用命令速查手册
  • DeepSeek HuggingFace 70B Llama 版本 (DeepSeek-R1-Distill-Llama-70B)