弱网络下的可靠UDP研究

前言

这是一个公司内组织的业余研究小组活动,我找了关于手游弱网相关的课题,并在为期12周的过程中,和专家导师一起细化方向,阅读相关材料,做了一些测试实验.

虽然因为时间有限,几乎没有太多深度,但是对于个人开拓视野颇有帮助,特别是跟着专家学习了做课题研究的方法方式,在这里记录一下历程。

第一周 开题

手游相比于客户端游戏, 在网络接入层增加了运营商(移动, 联通等)因素《一秒钟法则:来自腾讯无线研发的经验分享》, 移动用户会经常碰到"弱网络"环境, 具体表现为三种情况:

  • 信号强度弱(建筑死角, 隧道, 深山老林, 无线信号会衰弱).
  • 信号不稳定(在高速移动的火车上, 会有明显的多普勒效应, 网络延迟抖动).
  • 网络拥塞(人口密集的场所, 运营商降低人均通信带宽, 网络延迟增大). 简单来说, "弱网络"环境会有更高的丢包率, 更大的延迟抖动, 不稳定的网络连接.

实时 PVP 的战斗玩法会对网络的质量要求比较高, "弱网络"的这些表现都会影响到正常游戏的体验. 经过调研得知, 在兄弟团队的项目中, 基于帧同步实现的实时 PVP 战斗, 也存在平均延迟比较大, 不能随便移动等问题.

针对"弱网络"环境及其对游戏体验带来的影响, 本次研究希望改善下面两个问题:

  1. 问题一, 在高丢包率和不稳定的网络连接环境下, 如何保证网络的可靠性, 即数据的可靠及时传输.
  2. 问题二, 在网络延迟不稳定的情况下, 如何提高玩家的用户体验.

希望避开 TCP 的握手开销和拥塞控制算法, 使用可靠 UDP 做数据传输《M-UDP: UDP for Mobile Cellular Networks》.

业界使用 UDP 通信的游戏引擎很多, 例如 Unreal, 但是基本没有针对"弱网络"环境做优化. 这一块有很多事情可以做,比如通过代理和隧道技术屏蔽 IP 地址的变化, 以适应不稳定的无线网络《IP Mobility Support for IPv4》, RFC 3344.

第二周 Proposal

这周在与专家导师沟通之后,细化了方向,评估方法暂停为:

在各种典型网络环境下, 对比 TCP 长连接, HTTP 短连接, 以及本次课题研究中提出的方法, 对通信的稳定性和传输速率做测试, 最终输出对比测试报告.

第三周

在上周的集中讨论中, 经过导师的建议, 将研究课题细化为《基于可靠UDP的手游弱网络通信优化》, 并重新更新了 Proposal.

本周花了比较多的时间看 RakNet, RakNet 是一款 C++ 实现的 UDP 网络引擎, 由 Jenkins 开发, 后来被 OculusVR 收购之后, 基于 BSD 协议开源.

RakNet 比较适合集成到游戏中, 性能高, 支持多平台, 包括 Windows, Linux, Mac, iOS, Android 等.

这周看的资料包括了 RakNet Manual《Android RakNet 系列之六 源码说明》.

既然我们需要的是适合手游的可靠 UDP, 那么一定会需要这些特性:

  • 有连接的概念, 每个数据包有数据校验, 保证数据包对上层业务有序;
  • 不太可能完全抛开拥塞控制, 还需要做一定的控制策略, 对用户的流量负责.

后续要带着这些问题继续学习.

第四周

这周要做 Presentation, 所以优先读了一些 paper.

《A Reliable Transport Protocol for Real-time Control over Wireless Networks》, 这是一篇 2012 年发表在 IEEE ATNAC (Australasian Telecommunication Networks and Appication Conference) 会议上的 paper, 文中作者提出了一种新的传输层协议 XTP 来适应无线网络, 既保证了传输的可靠性, 和不错的端到端时延, 同时还对传输的数据提供了优先级选项, 来满足不同场景的需求. 总体来说有不少值得借鉴学习的思想, 具体的内容可以参考本次presentation.

在上面这篇 paper 中并没有提到关于拥塞控制算法相关的内容, 推断可能是没有包括这一块的实现, 毕竟是一个 connectionless 的协议, 这些都抛给上层应用层去处理.

在文中使用到了 Network Simulator 2 工具做网络仿真, 看上去似乎是个不错的工具.

除此之外, 本周还对比了另外一款开源 Reliable UDP 实现 UDT, 目前最新的版本是 UDT4, 最早是 Illinois 大学开发, 现在是 Google 在维护, 并且是基于 BSD 协议的, 对商业集成比较友好. UDT4 的代码相对 RakNet 精简一点, 可读性强.

在上次周会中, 曾经说到使用 UDP 的很大一部分原因, 是为了避免在移动网络环境下, TCP 的拥塞控制算法不太合适, 反而降低网络数据的传输速率.

rfc 2581 中详细描述了 TCP 的四种拥塞控制算法:

  • 慢启动
  • 拥塞避免
  • 快速重传
  • 快速恢复 这些算法相互交织在一起了, 一起构成了 TCP 的拥塞控制机制.

经过与导师的讨论, 得出一个初步的观点: 可靠 UDP 需要拥塞控制, 但是要适应移动网络环境, 所以需要在研究算法的基础上, 做一定取舍和改进.

第五周

本周继续阅读源码, 主要学习 UDT 的协议以及相关源码逻辑.

Bandwidth-Delay Product, 即 BDP, 可以直译为: 带宽-延迟-积.

BDP = bandwidth * delay (RTT).

In data communications, bandwidth-delay product refers to the product of a data links capacity (in bits per second) and its round-trip delay time (in seconds).

随着网络的发展, BDP 增大, TCP 协议会碰到一些效率问题, 因为它的AIMD(additive increase multiplicative decrease)算法彻底减少了 TCP 拥塞窗口,但不能快速的恢复可用带宽.

UDT 的设计目标, 是在 BDP 比较大的网络上(例如光纤网络)上, 提供稳定, 公平, 高效的数据传输, 即使带宽变化剧烈.

本次课题主要研究的是如何在不稳定的无线网络中, 优化网络传输, UDT 所追求的目标与本课题并不一致, 但是它的实现机制对我们来说还是很有参考意义的.

UDT 数据包格式

UDT 控制包格式

TCP 的一个重要的拥塞控制原则是 AIMD: Additive increase multiplicative decrease, 是指 TCP 在网络畅通时, 线性增大发送窗口(例如+1), 在网络拥堵时, 乘性减小发送窗口(例如/2), 保证了网络的公平性.

这个算法就决定了 TCP 在网络拥堵时无法快速恢复. 也是 UDP 可以拿来做改进的一个点.

第七周

这两周的大部分时间在学习 UDT 代码 UDT: A High Performance Data Transport Protocol, 并准备测试 demo.

UDT 核心思想

如上图所示, 左侧是典型的基于 TCP 的网络应用, 右侧则是 UDT 的应用. UDT 与 TCP 类似, 提供了:

  • 面向连接(Connection-oriented)
  • 可靠的(reliable)
  • 全双工(duplex)
  • 单播(unicast)
  • 数据流(data streaming).

这些在大部分的可靠 UDP 中都有类似的实现, 包括 Raknet 和 ENET.

UDT 使用了新的拥塞控制算法, 以及可配置的拥塞控制框架(Configurable congestion control framework).

之前的周报中有提到过, TCP 的拥塞控制算法基于发送窗口, 为了保证网络的公平性, 是 AIMD (Additive Increase, Multiplicative Decrease) 的:

x 代表发送窗口: 正常发包时发送窗口线性增大 (alpha = 1), 丢包之后发送窗口乘性减少 (beta = 0.5).

UDT 做的最大的改进就是调整了拥塞控制算法, 原文是 AIMD with Decreasing Increase, 即一开始就使用一个非常大的发送窗口, 并且随着时间的推移递减. 下面这张图可以说明的比较清楚:

UDT 的设计目的就是能在 BDP 大的高速网络环境中提高网络传输的效率, 所以这样的 AIMD 算法设计是合理且有效的.

paper 学习

近两周还读了一篇 paper: 《Experiences from Implementing a Mobile Multiplayer Real-Time Game for Wireless Networks with High Latency》.

这篇 paper 是跟一位同事讨论 UDP 问题时, 从一份测试数据中引申出来的, 发表于 2009 年, 比较偏工程化.

Paper 的前半部分讲述了分别在 GPRS, EDGE, UMTS 和 WLAN 环境下做的性能测试, 包括了 RTT 和 传输速率 两个维度, 后半部分以一个 BrickBlock 游戏为 demo, 讲述了一些同步算法和在移动环境下的取舍.

整篇 paper 对本次研究有参考价值的, 就是它的测试结果. 从下面的测试结果可以看出, 在移动环境(2G, 3G, Wifi)中, 不管是 RTT 还是 传输时间, UDP 协议都比 TCP 有优势.

实验 demo

因为我们目前的项目是基于 Unity3D 的, 所以从实际应用的角度出发, 考虑将 UDT 编译成 dll 在 Mono 环境下调用,目前看上去有点工作量.

第九周

上周把 UDT 集成到 Android 环境中, 并拿 UDT 与 TCP 做了基础的对比测试(没有调整默认参数). 暂时不打算集成到 Unity3D 环境中了, 引擎这一层的关系不是很大, 能在 Android 下跑测试, 暂时已经能满足我的需求了.

对于服务器开发来说, 配置 Android 环境实在是一件头疼的事情, 需要在各种平台和重量级 IDE 中折腾, 记录一下几个碰到的坑:

  1. Google 新出了一套 Android 的开发 IDE: Android Studio, Android 的官网上默认都开始推荐这一套 IDE 了, 虽然是 Google 官网出的, 但是国内的网络环境决定了它必然坑, 连编译都需要连 gradle 站点, 慢到崩溃的节奏. 知乎上也有关于 Android Studio 的讨论, 对于习惯了 Vim 写服务端程序的我来说, 智能提示和 UI 都可有可无, 所以最后还是回归了 ADT.

  2. 因为 UDT 是 C++ 实现的, 所以只能以 JNI 的方式来调用, 学习了很久怎么使用 JNI, 包括中间的各种潜规则. 比如使用 stl 则必须创建一个叫做 Application.mk 的文件, 并在里头指明: APP_STL := stlport_static, 这个编译选项为什么不能直接加在 Android.mk 中呢...

本周做了 PC 下 TCP 和 UDT 的对比测试, 分别对比了不同环境下的传输速率, 测试程序包括了一对持续收包和发包的 client & server.

测试用例中, server 部署在美国拉斯维加斯的机房, 延迟相对比较高(大于 100 ms), 且经常性波动. 客户端则配置了下面 6 种参数:

  • 默认参数的 UDT 客户端
  • 恒定发包速率 = 10mbps 的 UDT 客户端
  • 恒定发包速率 = 50mbps 的 UDT 客户端
  • 恒定发包速率 = 100mbps 的 UDT 客户端
  • 模拟 TCP 参数的 UDT 客户端
  • 原生的 TCP 协议客户端

测试场景一: 公司 staff wifi, ping 值在 130 ms 上下.

测试场景二: 公司 guest wifi, ping 值在 130 ms 上下.

测试场景三: 家里的电信 wifi, ping 值在 148 ms 上下.

在 wifi 相对稳定的大部分情况下, UDT 比 TCP 拥有更高的传输速率, 但是网络波动会比较大, 不如 TCP 平稳. 下周需要将测试客户端移植到终端, 来验证在 wifi 不稳定情况下的表现.

本周做测试时碰到了一个问题: 在体验网环境下的测试, TCP 的表现与 UDT 相比, 高出了一个数量级, 很不正常, 所以没有列在测试结果种. 没有找到原因在哪里, 初步怀疑跟体验网的网络限制有关, 为了防止下片儿做了 UDP 的限速?

结语

这次课题因为时间的原因,虎头蛇尾的结束了,几乎没有实质性的结果。

总结了一下自己在过去几周主要做的事情:

  • 读 paper 和代码,理解移动弱网,学习 UDT 的核心思想和原理
  • 方法论学习,从开题, Propasal, 计划安排,最终给出报告的完整流程

有学习的收获,也有缺失深入研究的遗憾,希望后续能利用个人时间做更进一步的学习和研究.

非常感谢这段时间中,几位专家牛人导师的指导和建议。