2010年10月11日星期一

RUDP

前几天用GRPS、3G上游戏,感觉很是不爽,因为网络丢包率太高,而且很容易断线,这导致游戏会频繁的卡住或要重新登录。于是我萌发了做一个可靠的UDP传输协议的念头。(这个念头很早就有,只不过这的确不是一个优先级高的工作,而且,似乎只有回合制这类网游才用的上它,所以迟迟没有去开发)

很多年以前,我就做过这玩意,但是现在的需求有些不一样,还是要重来。确切的说,这类协议现成的代码很多,我现在无非是重复了一遍造轮子的过程。不过,针对我的需求而言,这个轮子还是有必要重新造造的。

我对RUDP的需求如下:

  1. 网络断开并且恢复后,不能断开RUDP连接,即运行中可以更换IP;
  2. 在高丢包率的网络环境下可以正常的工作;
  3. 轻量级的代码,并且易于嵌入VM。
最后一点,是我不采用现成代码的缘故。现在代码要么过于沉重(大而全的功能总是如此),要么接口不太适合VM。

总的来说,开发这个并不难,但是也不容易。如果只想完成一个可以简单通讯的代码非常容易,只需要一天就够了,但要进行充分的测试,并且适应各种网络情况的话,这个工作就要复杂得多。网络上通讯,几乎所有的情况都需要考虑到,任何情况都有可能发生,这至少让代码复杂了三倍以上。事实上,我编码的时间的确很短,但是调试的时间十倍于编码的时间,远比其他模块的测试比重来得大。

在考虑如何嵌入VM的设计方案时,我足足考虑了两天,最终采用的思路是:做一个和BSD socket完全一样的一套接口,并实现了一个轮询用的函数。这样VM的代码几乎不需要任何复杂改动,只需要增加一个中间键,将socket操作demux到不同的接口即可,除此之外,在主循环进行轮询即可让RUDP模块工作。这里有一个缺陷,那就是因为轮询有时间间隔,可能会长达10ms,这样使得RUDP处理时,天然就会有可能多达20ms的延迟(两端各10ms)。不过这并不是大问题,因为对游戏来说这足够了。

相关实现如下:

  1. RUDP采用了两次握手而非三次握手,理论上这有一定的安全隐患(可以冒充客户端IP),不过不要紧,对游戏服务来说这并不是什么安全隐患;
  2. 有滑动窗口,和基于字节的sequence no和ack机制,这是为了保证和原有的TCP流传输方式一致,不影响上层实现;(如果是单独实现,基于报文方式显然更佳)
  3. 没有慢启动等算法的实现,也就是说传输端会全力发包而没有退让,这是谦逊的TCP经常会陷入奇慢甚至是假死的原因;(当然,谦逊的TCP能适应各种网络环境,从0.1K到1G带宽都可以自适应)
  4. 断开的FIN/ACK机制,这样可以保证发送数据后立刻断开仍然可以确保所有数据被传送到对端。

在我集成到游戏时非常顺利,没有遇到太大问题。登录成功以后我一度还以为RUDP没有工作,使用的仍然是TCP协议。不过看到LOG和断开网络时的反应,可以确认RUDP的确工作了。当然,这么顺利的原因并不是运气,而是之前做了大量的测试工作。只是,这仅仅是个人在实验室中的测试,随着测试人员增多,环境变复杂,应该会暴露一些问题。

由于正好赶上游戏第二次发号测试,所以包含RUDP协议的服务器端还不能发布,我还不能实测其效果。不过,我倒是在另外一个场合先应用上了这个工作的成果。

因为我不在国内,需要使用VPN登录进入内网访问版本服务器,速度很慢。编译版本需要取美术资源文件(>1G),几乎是一个无法完成的任务。因为TCP的谦逊品质,从samba服务器复制的速度会越来越慢,而VPN可能时不时会断开,这导致我只能在特定的时间进行下载,否则难以成功。我用脚本写了一个非常简单的文件传输工具,通过RUDP传输文件。这样,虽然只有一个连接,但是速度和Flashget、迅雷这一类用多个TCP连接下载的速度一样,我只需要做一个非常简单的续传功能(用来防止VPN长时间断开),就可以轻易的实现一个高速的文件下载服务。这个工具缺点就是,如果网络带宽很大,使用默认的参数不能充分的利用下载带宽。不过,如果带宽如此之好,用系统默认的文件工具不就行了吗?

没有评论:

发表评论