Main

October 18, 2020

cache server 问题总结

这周,我们的 cache server 服务面临了很多的挑战。项目资源超过了 30G ,有几十个用户在同时使用。每天都有版本切换工作(导致重新上传下载 30G 的数据)。在这个过程中,我对 cache server 程序修修补补,终于没有太大的问题了。

总结一下,我认为 cache server 的协议设计,以及 Unity 客户端实现,均存在很大的问题。这些问题是无法通过改进服务器的实现彻底解决的,只能做一些缓解工作。真正的完善必须等 Unity 的客户端意识到这些问题并作出改进。

cache server 的协议设计非常简陋。就是顺序的提交请求,然后每个请求会有序的得到一个回应。这些请求要么是获取 GET 文件,要么是上传 PUT 文件。其中 PUT 文件在协议上不必回应。

由于 PUT 文件没有回应,所以客户端无法直接确定文件是否全部上传完毕;如果必须确认,只能在 PUT 文件结束后,再提交一个 GET 请求。如果收到了后续 GET 的回应,可以理解为前一个 PUT 已经结束。实际上,Unity 客户端没想去确认 PUT 是否结束,从 log 分析,它只是简单的在最后一个 PUT 结束后等待了一段时间再断开连接。

PUT 实际上是个小问题,真正的问题是:这种依赖严格次序的协议,在面对两边数据量不对等、网络速度不对等的近况时,很难有一个健壮的实现。

May 16, 2019

通过对缓存测速提取信息的旁路攻击

最近爆出对 Intel 系 CPU 的新的安全漏洞。MDS 攻击 可绕过安全屏障,取得同一个核心上运行的其它进程(或其它虚拟机)处理的数据。安全界的建议是暂时关闭超线程,避免泄露信息。

我对这个安全漏洞颇感兴趣,花了一些时间研究,弄明白了来龙去脉。

其实,新的 MDS 攻击的思路继承至更早发现的 Meltdown 攻击方式。除了原始论文,我找到了 这篇 blog 把其原理介绍的非常清晰。

如果不介意我可能有理解偏差,那么可以看看下面我对其原理做的一点中文复述:

March 15, 2018

为什么用本地程序通过本地端口做第三方服务认证是不安全的

今天有同事吐槽钉钉的 windows 客户端做第三方服务权限认证的流程,人机交互方面远没有 qq 好用。

我说,通过一个普通权限的本地程序做统一认证,其实是很容易出安全漏洞的,小心点比较好。一般来说,这个在操作系统层面支持会比较安全,就像 windows 的 UAC 。这种通常是第三方应用向服务器发一个认证请求,然后服务器下转发到本地客户端,然后客户端弹出一个确认窗口,经过用户确认以后,再经由第三方服务器下发给那个第三方客户端。

这里有个安全隐患就是,如果这个弹出窗口不是操作系统级别支持的话,在 windows 下很容易被普通权限的同级程序拦截。当然也不是完全没有办法。比如预留一个用户认可的信息展示,好像信用卡那样的安全识别码;我没用过 qq ,听说 qq 是用用户自己的头像做防伪确认的。

不过,这套流程做起来比较麻烦,开放个第三方使用的话,需要第三方客户端/服务器都遵循一定的协议来做。而且第一次需要做一次账号绑定,需要用户在第三方应用里输入一次自己的 qq 号,或在 qq 中输入一次第三方账号。windows 下可以先用 FindWindow 找到 qq 客户端的窗口,然后用一个自定义消息把一个 token 或第三方的账号信息发过去,完成握手。

说到这里,同事说,qq 的那套似乎没那么复杂,好像是走的本地端口。我先想说不至于吧,但是似乎每次遇到安全问题,我都会高估腾讯的产品设计人员的安全意识下限。腾讯系产品的用户权限大量被盗用似乎在黑产链上不足为奇。

July 21, 2017

防止深度包检测的一个方法

虽然以现在的加密技术,主要选择的加密算法没问题,在很长一段时间都不太用担心监听通讯的人解密获得明文。但是针对特定的加密通讯协议,还是很可能找到方法找到某种模式。这个模式不能转换为明文,但可以猜测出你是否在使用特定协议。

另外,无论你怎么加密通讯,访问特定服务流量的时间特征也可能泄露你的秘密:用什么节奏通讯,每个 ip 包多大,这些都是可供匹配的特征。

我认为,大多数情况下,通讯的稳定性是大于带宽的需求的。那么,采用本文这种方法应该能去掉上面这些流量特征。

March 11, 2016

可靠 UDP 传输

本文分三个部分:一,什么时候有可能采用 UDP 通讯而不是用 TCP 更好;二,一个可靠的 UDP 通讯模块的 API 接口该如何设计;三,一个简单的实现。


首先,我一直是非常反对在 UDP 协议上实现一个可靠传输协议的,即类似 TCP over UDP 的东西。

TCP 已经够复杂了,几乎不太可能重新设计的更好。如果用 UDP 再实现一个可靠传输协议,而表现的比 TCP 效果更好,那么多半只是在部分情况下的优势;或是霸道的占用了过量的资源,而 TCP 在设计时则是很友好的,以整个网络的通畅为更高准则的。

对于后者,我心里相当排斥。如果大家都想独占网络带宽,那么只会让每个人都无法获得高质量通讯。

在网络游戏,尤其是移动网络上的网络游戏制作圈里,不断的有人期望基于 UDP 协议通讯来获得更快的响应速度,而又想让通讯流像 TCP 一般可靠。我也时常思考这个问题,到底该怎么做这件事?

如果基于 UDP 可以做的比 TCP 更好,那么一定是放弃了点 TCP 需要做到的东西。

一条路是寄希望于业务逻辑上允许信息丢失:比如,在同步状态中,如果状态是有实效性的,那么过期的状态信息就是可丢失的。这需要每次或周期性的全量状态信息同步,每个新的全量状态信息都可以取代旧的信息。或者在同步玩家在场景中的位置时可以用这样的策略。不过在实际操作中,我发现一旦允许中间状态丢失,业务层将会特别难写。真正可以全量同步状态的场合也非常少。

那么,不允许信息丢失,但允许包乱序会不会改善? 一旦所有的包都一定能送达,即丢失的包会用某种机制重传,那么事实上你同样也可以保证次序。只需要和 TCP 一样在每个包中加个序号即可。唯一有优势的地方是,即使中间有包晚到了,业务层有可能先拿到后面的包处理。

什么情况下是包次序无关的呢?最常见的场合就是一问一答的请求回应。采用这种方式的, UDP 在互联网上最为广泛的应用,就是 DNS 查询了。

在网络状况不好的时候,我们可以看到有时采用短连接反而能获得比长连接更好的用户体验。不同的短连接互不影响,无所谓哪个回应先到达。如果某个请求超时,可以立刻重新建立一条新的短连接重发请求。这时,丢包重发其实是放在业务层来做了。而一问一答式的小数据量通讯,正是 TCP 的弱项:正常的 TCP 连接建立就需要三次交互,确定通讯完毕还需要四次交互。如果你建立一次通讯只为了传输很少量的一整块数据,那么明显是一种浪费。这也是为什么 google 的 QUIC 对传统的 http over TCP 有改善的空间。

我的思考结论就是:在 UDP 协议之上,实现一个带超时的请求回应机制,让业务层负责超时重发,有可能取得比 TCP 通讯更好的效果。但其前提是:单个请求或回应的包不应该过大,最好不要超过一个 MTU ,在互联网上大约是 500 多字节。

September 19, 2015

说说 XcodeGhost 这个事

最近 XcodeGhost 这个事挺火的,简单说就是有人在 Xcode 里植入了木马,让用这个被修改过的 XCode 编译出来的 ios app 都被插入了一些代码。由于 XCode 几乎是生成 ios app 的唯一手段,且墙内的同学各种诸如用 baidu 搜索,迅雷下载等坏习惯早就根植在基因里了,导致了这份被种马的 XCode 在最近半年里广泛传播,污染了一大批国产 app 。

我这里就不提那些中招的所谓大厂了,如果经常听我扯淡的同学早就知道我的观点:即便是大厂,有安全常识的人还是少之又少、反而是所谓大厂因为管理更困难(好多大厂到管事的那级的人更跟不上知识更新),犯安全错误的机会就更大。

这种从非官方渠道下载软件使用而中招的事情又不是第一次了。上次 putty 被种后门 的事情过了也没多久,好了疮疤忘了痛,对吧。

种马的手法其实也不新鲜。有点年纪的程序员读书时,课本上一定教过:计算机病毒有四种形式,最后一种就叫源码病毒,感染附着在编译器中。只是估计很少人见过,后来课本就删掉了。其实呢,这个说法的源头是 Ken Thompson 在 1984 年图灵奖演说上提到过的恶作剧。老实说 The Ken Thompson Hack 的手法比这个高明的多。

July 09, 2015

n:m 的 vpn 隧道

前两天成功用 mptcp 搭建好出国上 github 的快速通道后 ,在想如何方便的无缝使用。如果不使用 vpn 的话,就无法用路由规则来把流量倒向隧道。当然修改内部 dns 的 github 域名解析是可以的,但终归不是很舒服。

SA 试了一下用 PeerVPN 搭 vpn tunnel ,工作倒是正常,但是 mptcp 跑在隧道上等于是白费了。PeerVPN 是用 UDP 通讯的,实际上只走了单条路径,如果再在上面跑 mptcp 等于是自欺欺人。

所以也曾经考虑过使用基于 TCP 连接的 VPN ,比如 openVPN 。在 SA 尝试做配置的时候,我突然有了个想法。既然 PeerVPN 是开源的,何不修改一下实现,让它支持多个 ip 间建立多条路径的通道呢。

July 06, 2015

使用 MPTCP 增加对 github 的带宽

从去年开始,我们的工作项目逐步迁往 github 的私有仓库里。github 太好用了,自己搭建的 git 平台完全比不上。可是购买企业版本自己架服务器成本又太高(平均一个人一年要 250 刀),而我们项目并不多(只是人多)购买私有仓库绝对够用了,唯一的缺点就是 github 没有在国内开展业务,服务器都在境外,速度很慢。

之前我们无论是电信还是联通线路,从 github 上 clone 项目的速度一直没有超过 200KB/s ,而我们租用的国际线路本身带宽又很低(因为单价太高,所以只做部分翻墙用)。而且连接还不太稳定,有一定的丢包率。

考虑到我们办公室租有多条不同运营商的宽带,所以最近在考虑怎么把这些资源整合在一起使用。

带宽的瓶颈显然是在墙上,所以只要提高过墙的带宽就可以了。我在 linode 机器 上做了测试,从 linode 美国机房 clone github 仓库的速度轻易可以达到 8MB/s 。

一开始我的念头是自己写一个支持多条 TCP 连接协作的 tunnel 程序。后来转念一想,MPTCP 不就是干这个用的么?去年的时候玩过一阵 MPTCP ,这次和我们爱折腾的 SA 同学们一起再试试。

January 08, 2015

为什么 skynet 提供的包协议只用 2 个字节表示包长度

skynet 提供了一个 lua 库 netpack ,用来把 tcp 流中的数据解析成 长度 + 内容的包。虽然使用 skynet 的同学可以不使用它,而自己另外实现一套解析协议来解析外部 TCP 数据流(比如 skynet 中的 redis driver 解析 redis server 的数据流就是用的换行符分割包),但依然有很多同学询问,能不能自定义包头长度。

这里的这个库定义的协议中,包长度是用 big-endian 的 2 个字节表示的,也就是说一个包的长度不得超过 64K 。这让很多人很为难。已经几次有同学建议,把长度放宽成 4 个字节,因为他们的应用环境中大部分包不会超过 64K ,但总有少量超过这个限制的。

July 11, 2014

计划给 skynet 增加短连接的支持

不基于一个稳定 TCP 连接的做法,在 web game 中很常见。这种做法多基于 http 协议、以适合在浏览器中应用。

运行在移动网络上的游戏,网络条件比传统网络游戏差的多。玩家更可能在游戏进行中突然连接断开而导致非自愿的登出游戏。前段时间,我实现了一个库来帮助缓解这个问题

如果业务逻辑基于短连接来实现,那么也就不必这么麻烦。但是缺点也是很明显的:

每对请求回应都是独立的,所以请求的次序是不保证的。

服务器向客户端推送变得很麻烦,往往需要客户端定期提起请求。

安全更难保证,往往需要用一个 session 串来鉴别身份,如果信道不加密,很容易被窃取。


即使有这些缺点,这种模式也被广泛使用。我打算在下个版本的 skynet 中提供一些支持。

所谓支持、想解决的核心问题其实是上述的第三点:身份验证问题;同时希望把复杂的登陆认证,以及在线状态管理模块可以更干净的实现出来。

我不打算基于 HTTP 协议来做,因为有专有客户端时,不必再使用浏览器协议。出于性能考虑,建立了一个 TCP 连接后,也可以在上面发送多个请求。仅在连接状态不健康时,才建议新建一个 TCP 连接。

June 11, 2014

一个适用于腾讯开放平台的 tunnel

目前如果要与腾讯合作运营游戏的话,是必须把服务器运行在腾讯的云平台上的。鉴于腾讯的强势地位,严禁自己托管机器,也禁止选择别家的云服务。至于腾讯开放平台,谁用谁知道,呵呵。既然都叫”开放“了,你就能想像是什么意思,所谓共和国还要在名字前加个 “人民” 呢 :)

如果你把 “必须运营在腾讯云平台” 这条当成企鹅税来看待,也就了然了。无非是多花点钱用差点的硬件,增加点程序员人工为它写些代码适配,出点啥莫名其妙的问题,忍忍,多吐点槽就好了。要理解大公司部门多,各个部门都需要完成自己的 KPI ,刷点存在感。

当然也有可能你的程序无法获得需要的处理能力,或是你不堪忍受在云平台上的各种奇葩。那么还有一条路可以偷偷走。那就是想办法自己另外托管机器,然后写一个 tunnel 程序把服务器连起来。

鉴于腾讯开放平台的开放性,普通现成的 tunnel 程序可能满足不了需求。晓靖同学最近在学 go 语言兴致比较高,用 go 实现了一个。需要的同学可以自取

这个东西完成了这样的功能:

February 08, 2014

在移动网络上创建更稳定的连接

我们的手机游戏发布有一段时间了。立项之前我写的一篇 blog , 在移动设备上开发游戏需要克服的两大技术难点: 移动网络的不稳定性以及手机硬件资源的约束。由于开发时间所限,第一点我们并没有专门去做。

我一直不想动手去做一个临时方案解决 TCP 断线重连问题,因为实现一个 TCP over TCP 是没有太大意义的。移动网络发展迅速的今天,整个行业都在努力提高移动网络的稳定性,所以费力做这个事情很可能在两年之后就变得完全没有必要。

比如,iOS 7.0 发布 后,让 MultiPath TCP 技术为更多人所知。从许多中文资料对其的解读,主要集中在 MPTCP 提供了更大的带宽上;甚至一些网络喷子借机来喷国内的 3G 收费高的问题,认为同时利用 3G 网络和 wifi 下载没有意义。但我认为其对于移动网络的真正意义在于提供一个更加稳定的连接。

顾名思义,MPTCP 允许在同一 TCP 连接的通讯两端建立多条通讯路径,如这篇文章 所言:Just like IP can hide routing changes, MPTCP can hide the details of which paths it is using at any given time.

这两天,我们在自己的服务器上安装了支持 MPTCP 的新内核做了测试。发现:如有可能,设备会为新的 IP 地址建立新的通讯路径。如果连接两端各有两个 IP ,那么在初始的 TCP 连接建立后,通过协商,最终会建立 4 条 TCP 连接出来,交叉连接了所有的 IP 。任何一条通路有效都不影响通讯。btw, 如果你的机房有网通,电信两个 IP 的话,如果客户端设备支持 MPTCP ,那么会自动同时使用两个通路同时维持一个逻辑上的连接。这对国内的网络环境非常有利,不需要使用 bgp 机房,也不需要在多线机房配置复杂的 DNS 了。

当你的手机从 3G 网络切换到新的 wifi 热点时,设备会自动利用新的 wifi 网络做数据传输;离开 wifi 热点后,又能无缝切换回 3G ;再次进入新的 wifi 热点范围,还可以重新利用新的 wifi 网络。这样,移动设备可以穿梭于多个网络之间而永不断开连接。

可惜的是,Apple 目前并没有完全开放 MPTCP 给应用层使用。经我的测试,只有 Siri 的连接才会发送 MPTCP 握手协商。这篇 blog 也证实了这一点

ps. 经过这两天的测试,还发现 MPTCP 似乎只能利用第一次连接的通路做控制信息交换。当第一次连接的 IP 实效后,不能把后来的通路提升为主控连接。所以 MPTCP 看起来不能在只有一个网络设备上正常工作。(我原先预期它可以在同一个设备上切换 IP 还可以正常建立新的子流,看来是搞错了)

November 11, 2013

虚惊一场

周五的时候,肥龙同学偶然发现我们机房的内部网段出现了一个奇怪的 ARP 广播,一个并不是我们配置的 ip 地址 192.168.0.120 不断在交换机上广播。经扫描,它提供了包括 ssh/http/https/vnc 的服务,但是我们每次想连接上去看看是什么服务的时候,都会把连接断开,同时,这个 ip 对应的 mac 地址在不断变化。

各种奇怪的现象似乎在说,我们被攻击了。

我们的防火墙规则还是非常严格的,系统安全方面也很注意。我觉得即使被人下了木马,也不大可能被大面积入侵。何况有几台机器从外网不可能进入,如果需要感染这些机器,必须先突破允许外网连接的机器才行。

December 07, 2012

登陆认证系统

最近在忙代理项目 狂刃 的事情。

因为这个项目,我们提前建立了平台开发团队。但许多东西开始的都很仓促,比如需要对接用户登陆认证系统。

虽然已经有很多成熟的认证协议,比如最有名的 kerberos 。但这次时间紧迫,我就临时设计了一个简单协议。

因为不是 web 应用接入,所以我不想直接使用 https 来提交用户名和密码,而基于 http 协议,在不安全信道上建立了一个自定义协议来应付一下。这种临时设计的协议当然不会很缜密,但也基本够用。

这样,合作方的客户端可以较容易实现相应模块。

April 09, 2012

如何更准确的网络对时

这个周末 想到这个问题, 是由另一个问题引起的.

为了模拟复杂的网络环境,我们在内网安装了模拟环境

怪物公司同学周末调试客户端时,修改了自己机器的网关,增加了模拟延迟。奇怪的是,他的客户端在切换网关时并没有断开连接。可延迟也果真发生了。

我和他探讨了一下,觉得这个模拟延迟是单向的。当游戏服务器发送数据包回桌面时,由于服务器和他的桌面机在同一个网段,所以 IP 包被直接发回了。TCP 连接也不因为修改了过去的通路和断开。

当然,这种模拟并不是我们想要的。正确的方法应该是在修改网关(指向延迟模拟机器)的同时,也修改桌面机的 IP ,或是给自己机器绑定两个 IP ,使用模拟环境网段的 IP 来重新建立 TCP 连接。或者在模拟网关上做一次 NAT 。反正方法有很多,不展开讨论了。只有正确的模拟双向延迟(或网络颠簸)才好得到接近现实情况的场景。

不过这次错误,引出我另一个思考。如果 TCP 上行和下行延迟差距较大,有没有什么特别糟糕的事情发生呢?我的第一反应是,网络对时不准了。

我们的对时协议一般都遵从这样一个假定。我们的桌面机发送一个数据包到服务器所需要消耗的时间,大约等于服务器发一个数据包回桌面机的时间。这样,我们测试出一个数据包来回的时间,除 2 ,就得到了单程时间。这样就可以根据时间服务送来的服务器时间,把桌面时间和服务器时间基本校准了。

可一旦上下行速度不一致,甚至偏差较大时,这个假定被破坏掉了,时间也无法校准了。

April 06, 2012

Ringbuffer 范例

前段时间谈到了 ringbuffer 在网络通讯中的应用 。有不少朋友写 email 和我探讨其实现细节。

清明节放假,在家闲着无聊,就实现了一个试试。虽然写起来还是挺繁杂的,好在复杂度还在我的可控范围内,基本上也算是完成了。

设想这样一个需求:程序 bind 并listen 一个端口,然后需要处理连接到这个端口上的所有 TCP 连接。当每个连接上要数据过来时,收取这些数据,识别出封包,发送给对应的逻辑层处理。如果数据不完整,则暂时挂起这些数据,直到数据收取完整再行处理。

February 02, 2012

Ring Buffer 的应用

这是一篇命题作文,源于今天在微薄上的一系列讨(好吧,也可以说是吵架)。其实方案没有太多好坏,就看你信不信这样做能好一些或坏一些。那么,整理成 blog 写出,也就是供大家开拓思路了。

我理解的需求来源于网络服务提供程序的一个普遍场景:一个服务器程序可能会收到多个客户端的网络数据流,在每个数据流上实际上有多个独立的数据包,只有一个数据包接收完整了才能做进一步的处理。如果在一个网络连接上数据包并不完整,就需要暂时缓存住尚未接收完的数据包。

问题是:如何管理这些缓冲区比较简洁明了,且性能高效。

其实这个有许多解决方案,比如为每个网络连接开一个单独的固定长度的 buffer 。或是用 memory pool 等改善内存使用率以及动态内存分配释放,等等。今天在微薄上吵架也正是在于这些方案细节上,到底好与不好,性能到底如何。既然单开一篇 blog 了,我不像再谈任何有争议的细节,仅仅说说,用 Ring Buffer 如何解决这个问题。

January 20, 2012

libuv 初窥

过年了,人都走光了,结果一个人活也干不了。所以我便想找点东西玩玩。

今天想试一下 libev 写点代码。原本在我那台 ubuntu 机器上一点问题都没有,可在 windows 机上用 mingw 编译出来的库一个 backend 都没有,基本不可用。然后网上就有同学推荐我试一下 libuv 。

libuv 是 node.js 作者做的一个封装库,在 unix 环境整合的 libev ,而在 windows 下用 IOCP 另实现了一套。看起来挺满足我的玩儿的需求的。所以就试了一下。

January 11, 2012

铁路订票系统的简单设计

其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。

本来我在 weibo 上闲扯两句,这么简单的方案,本以为大家一看就明白的。没想到还是许多人有疑问。好吧,写篇 blog 来解释一下。

简单说,我们设置几个网关服务器,用动态 DNS 的方式,把并发的订票请求分摊开。类比现实的话,就是把人分流到不同的购票大厅去。每个购票大厅都可以买到所有车次的票。OK ,这一步的负载均衡怎么做我就不详细说了。

每个网关其实最重要的作用就是让订票的用户排队。其实整个系统也只用做排队,关于实际订票怎么操作,就算每个网关后坐一排售票员,在屏幕上看到有人来买票,输入到内部订票系统中出票,然后再把票号敲回去,这个系统都能无压力的正常工作。否则,以前春运是怎么把票卖出去的?

我们来说说排队系统是怎么做的:

December 14, 2011

pbc 库的 lua binding

前几天写的 pbc 初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库,就是很自然的工作了。

写完了之后,我很好奇性能怎样,就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题,因为测试用的数据实在是太简单了,等明天有空再弄个复杂点的来跑一下吧。我很奇怪,为什么 google 官方的 C++ 版性能这么差。

我的 lua 测试代码大约是这样的:

local protobuf = require "protobuf"

addr = io.open("../../build/addressbook.pb","rb")
buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)

for i=1,1000000 do
    local person = {
        name = "Alice",
        id = 123,
    }
    local buffer = protobuf.encode("tutorial.Person", person)
    local t = protobuf.decode("tutorial.Person", buffer)
end

100 万次的编码和解码在我目前的机器上,耗时 3.8s 。

December 01, 2011

Protocol Buffers for C

我一直不太满意 google protocol buffers 的默认设计。为每个 message type 生成一大坨 C++ 代码让我很难受。而且官方没有提供 C 版本,第三方的 C 版本 也不让我满意。

这种设计很难让人做动态语言的 binding ,而大多数动态语言往往又没有强类型检查,采用生成代码的方式并没有特别的好处,反而有很大的性能损失(和通常做一个 bingding 库的方式比较)。比如官方的 Python 库,完全可以在运行时,根据协议,把那些函数生成出来,而不必用离线的工具生成代码。

去年的时候我曾经写过一个 lua 版本的库 。为了独立于官方版本,我甚至还用 lpeg 写了一个 .proto 文件的解析器。用了大约不到 100 行 lua 代码就可以解析出 .proto 文件内的协议内容。可以让 lua 库直接加载文本的协议描述文件。(这个东西这次帮了我大忙)

这次,我重新做项目,又碰到 protobuf 协议解析问题,想从头好好解决一下。上个月一开始,我想用 luajit 好好编写一个纯 lua 版。猜想,利用 luajit 和 ffi 可以达到不错的性能。但是做完以后,发现和 C++ 版本依然有差距 (大约只能达到 C++ 版本的 25% ~ 33% 左右的速度) ,比我去年写的 C + Lua binding 的方式要差。但是,去年写的那一份 C 代码和 Lua 代码结合太多。所以我萌生了重新写一份 C 实现的想法。

做到一半的时候,有网友指出,有个 googler 最近也在做类似的工作。μpb 这个项目在这里 。这里他写了一大篇东西阐述为什么做这样一份东西,大体上和我的初衷一致。不过他的 api 设计的不太好,我觉得太难用。所以这个项目并不妨碍我完成我自己的这一份。

June 27, 2011

DNS 隧道

今天在 greader 上看到这么一篇,用 DNS 隧道实现免费上网。我的思绪猛然回到去年去新西兰度假的日子。

有那么几天,我们在惠灵顿的海边山顶租了个屋子,一切都很舒服,但是不能上网。甚至于附近连 wifi 信号都收不到,想"借用"一下别人的 wifi 热点都不成。我顶着海边的狂风在院子里竖起天线,捕捉着周围微弱的信号,最终未果。然后转战屋里的有线电视。我发现和国内的有线电视一样,机顶盒是接有网线的。也就是说,物理上,存在一条链路接入了互联网。但是我插上电脑后,发现 ip 包根本发不出去。不过,好似有个 DNS 服务是可以用的。

当时也没多想,只是觉得有办法可以利用一下。不过隔天就搬走了,没有深入下去。今天回味一下,感觉的确可以利用 DNS 服务和外部建立连接。当然,一开始就需要在外界把接应的程序程序搭建好。

June 24, 2011

使用 luajit 的 ffi 绑定 zeromq

最近 Lua 社区非常活跃。6 月 22 日发布了 Lua 5.2.0 (beta-rc2) 。今天(6 月 24 日) 发布了 LuaJIT-2.0.0-beta8 。

虽然 luajit 和 lua 5.2 还有点小矛盾,luajit 没有完全支持 lua 5.2 的迹象。不过,这些对 Lua 社区都是好消息啦。可能对于 lua 用户会有点小纠结,到底是追随官方的 5.2 版呢,还是去用性能更好的 luajit2 。我比较在意性能,暂时先投靠 luajit 了。反正和 5.2 区别也不大。更重要的是,luajit2 提供的 ffi 库相当之好用,极大的减少了我们写 C 库的 lua binding 的负担。从某种角度可以看到另一个问题,为基础设施模块设计出良好的 C 接口(而不是 C++ 的)是多么的重要。

zeromq 是用 C++ 实现的,但它提供的是简洁纯粹的 C 接口。这让它相当利于 binding 到其它语言中使用。之前,已经有了成熟的 lua-zmq 可供使用。它分别实现了 ffi 和不带 ffi 的版本。不过也正因为此,封装层包裹的很淡疼。如果只支持 ffi 版本的话,其实这个工作可以做的非常简洁。

出于实践 luajit ffi 库的目的,也为了让这部分代码看起来清爽一点。我花了半个下午自己封装了一下 zeromq 。所用时间比在 windows 下配置安装那个现成的 lua-zmq 所用时间看起来更少(不需要装 msys ,cmake 等等淡疼的玩意)。谁再下面留言说不要重复造轮子了,我也不打算跟它急了。吵架的时间都比写代码时间长。我们从来不会把写一遍 hello world 看成重新制造轮子不是么?使用 ffi 去 binding C 库实在是太容易了,不比写 hello world 更复杂。

May 27, 2011

聊天信息加密的乱想

信息加密技术已经很成熟了。不过想把加密信息伪装隐藏在看似明文的信息中的工具我还没有见到。

我的意思是,监听方完全察觉不到有密文在传输的情况下,把加密信息传输给对方。我记得有工具可以在图片中隐藏一些信息,即使图片经过扫描,隐藏在其中的密文信息依然可以读出。

May 18, 2011

Bitcoin 的基本原理

昨天读到了 Bitcoin 的中文介绍,觉得非常有意思。不过上面这篇文章解释的非常不靠谱,我花了一晚上去Bitcoin的官方网站 仔细研究了一下,总算理解了其原理。感觉非常有启发,尤其是对虚拟货币的流通和发行有许多借鉴意义。今天写这篇 Blog 理一下。

什么是货币呢?货币就是商品(包括服务)交换的媒介。现在我们通行的货币是由有信誉的银行发行的,基本上是由其信誉来担保的。只要用的人都认可,那么我们就可以用它来交易。货币有一定的保值特性,我把我的劳动/服务/所有的商品换成货币后,银行担保我在日后的某一天,我还可以用它交换会差不多等值的东西。这个保证的前提是,银行不会滥发新的货币以及大家都信任这一点。

March 01, 2011

分享文件服务

今天一个朋友用 qq 邮箱给我发了个 200+ M 的大文件,我无法提取。这让我意识到,在网络上分享大文件也是个比较大的需求。此类服务很多,历史也很悠久(很多网盘也被用户用来解决此问题)。但好用的并不多。

记得 opera 的新版也提供了分享本机文件的功能,可能是墙的原因,在国内也很难用。

我觉得如果为公众提供一个方便的渠道(又尽量压缩成本)来分享自己机器上的文件,或许会有许多用户的。关键在于便捷,功能纯粹。

最简单的方法是让发送文件方在本机架一个 web server ,然后把文件放到 web server 可以管理的文件服务目录中去。这个方案最好实施,对于懂行的同学来说,windows linux 等都提供有 httpd 服务,设置一下即可。对于不懂的同学,提供一个小型软件安装一下也很容易。而文件接收方仅需要用浏览器下载文件即可。

但是这个方案有很大的缺陷。大多数人机器都在防火墙背后,也没有权限可以在网关上设置 NAT 。很大可能是无法使用的。另外,架设 server 隐藏有一定安全上的风险。

而我的想法是这样的:

February 25, 2011

ZeroMQ 的模式

在需要并行化处理数据的时候,采用消息队列通讯的方式来协作,比采用共享状态的方式要好的多。Erlang ,Go 都使用这一手段来让并行任务之间协同工作。

最近读完了 ZeroMQGuide。写的很不错。前几年一直有做类似的工作,但是自己总结的不好。而 ZeroMQ 把消息通讯方面的模式总结的很不错。

ZeroMQ 并不是一个对 socket 的封装,不能用它去实现已有的网络协议。它有自己的模式,不同于更底层的点对点通讯模式。它有比 tcp 协议更高一级的协议。(当然 ZeroMQ 不一定基于 TCP 协议,它也可以用于进程间和进程内通讯。)它改变了通讯都基于一对一的连接这个假设。

ZeroMQ 把通讯的需求看成四类。其中一类是一对一结对通讯,用来支持传统的 TCP socket 模型,但并不推荐使用。常用的通讯模式只有三类。

July 19, 2010

游戏多服务器架构的一点想法

把网络游戏服务器分拆成多个进程,分开部署。这种设计的好处是模块自然分离,可以单独设计。分担负荷,可以提高整个系统的承载能力。

缺点在于,网络环境并不那么可靠。跨进程通讯有一定的不可预知性。服务器间通讯往往难以架设调试环境,并很容易把事情搅成一团糨糊。而且正确高效的管理多连接,对程序员来说也是一项挑战。

前些年,我也曾写过好几篇与之相关的设计。这几天在思考一个问题:如果我们要做一个底层通用模块,让后续开发更为方便。到底要解决怎样的需求。这个需求应该是单一且基础的,每个应用都需要的。

正如 TCP 协议解决了互联网上稳定可靠的点对点数据流通讯一样。游戏世界实际需要的是一个稳定可靠的在游戏系统内的点对点通讯需要。

我们可以在一条 TCP 连接之上做到这一点。一旦实现,可以给游戏服务的开发带来极大的方便。

May 11, 2010

silenceisdefeat 关掉了 TCP Forwarding

自从我自己购买了 linode 的 vps 后,我就没再用过免费的 ssh server 翻墙。

今天有同学问起,说 silenceisdefeat 不能用了。我登上去看了一下,在 /etc/ssh/sshd_config 里加了一句

AllowTcpForwarding no

嗯,估计是觉得好多人用它来做 proxy ,流量受不了,关掉了。

如果还想继续用怎么办呢?

September 29, 2009

有点神经过敏

最近网上流传着许多关于 SSL 安全性的传言。由于大多数相关链接由于总所周知(?)的原因,不能访问。大家自己搜索 "GFW发飙,SSL窃听?" 这篇文章读。里面提到这么一个东西,据说 "妄想使用Gmail等采用HTTPS登录的Webmail逃避检查也是徒劳的,UTM Plus内置的SSL代理可以截断所有单向验证的SSL请求,对解密后的内容进行控制、审计。也就是说,利用这个代理,一切基于SSL隧道的应用协议都被纳入控管范围,不会再有漏网之鱼。"

我初看时,不以为然。不就是做个假证书做中间人攻击么?关键还在于用的人自己要有足够的安全意识。

June 28, 2009

玩了一下 ActionScript

周末。

玩了一下 ActionScript 。因为感觉做一些简单的需要长连接的互联网应用,flash 是一个不错的选择。在大多数情况下,比要求用户安装一个客户端要人性。(当然,和要求用户为浏览器安装一个莫名其妙的 ActiveX 控件相比,让用户自己决定是否下载独立客户端要友好的多)

因为,虽然 Flash 大多数情况下作为一个浏览器插件(在 Windows 下是一个 ActiveX 控件)的形式存在,但其安全性比之许多绿霸之流的流氓软件还是值得信任的。

January 12, 2009

在不安全的网络环境下安全上网

偶尔,我会在公众场合上网。但是不敢以自己的身份登陆任何网站。这年头,自己家的机器都不安全了(使用非 Windows 平台的除外),哪敢信任不知底细的机器啊。

话说,我是没买过没用过笔记本的,对这个东西比较抵触。之前已经极大的挖掘了 Palm 手机的功能,比如可以用 ssh 登陆远程自己的机器。可以用浏览器访问一些简单的控制界面等等。只要在自己放在公网上的服务器配置好了,问题都不大。记得有一天,我想看一个网站上的图片,但是手机浏览器对那个网站的页面罢工。我是先 ssh 登陆到自己服务器,用 wget 下载下页面,再用 grep 分析 html ,然后 wget 下图片。并用 ImageMagick 缩小,压缩转换格式。最后放到自己的 web server 上,让手机浏览器可以顺利观看的。(顺便节约了许多 GRPS 流量)

这套方案用起来过于繁琐,也有很大的局限性。

昨天睡觉的时候胡思乱想。如果能弄一套通用的东西,或许就可以让我放心的去用别人的机器了。比如出差的时候突然需要上网,光靠手机又不够的时候,可以冲到网吧里暂时用一下。

November 18, 2008

利用 ssh 和 vtund 接入别人的局域网

最近在帮另一办公室的同事调试程序,有些东西远程弄起来比较麻烦,征得同意后,我希望直接连入对方的局域网来弄。但是申请 VPN 权限以及修改对方路由的程序比较繁琐,所以我想找个简单的方法。

首先我在我们办公室的网关上做了个 NAT ,让对方可以 ssh 到我的机器。

阅读 ssh 的手册我发现 openssh 支持一个 -w 参数,用来连接两端的 tun 设备。不过试了半天没有搞定 :( 所以又想了其它办法。

November 13, 2008

freebsd 下的 traceroute

今天调公司里的 VPN 时,发现我的 freebsd 机器 traceroute 老是失败。

控制台报告

traceroute: sendto: Permission denied

(以上错误信息用来引导 google 同类问题的同学)

一开始是 ping 都不行,我查看了 firewall 的设置,允许 icmp 包通过。ping 就可以用了,但是 traceroute 还是不行。

这让我很疑惑,后来用 tcpdump 查了一下,发现 freebsd 的 traceroute 默认是用 udp 协议做的。真是惯性思维害人啊。windows 和 linux 下都是使用 icmp 的。

man 了一下,发现 freebsd 的 traceroute 可以用 -P ICMP 选择 icmp 协议。然后一切正常,不需要修改 firewall 的设置了。

September 10, 2008

远程设置防火墙要小心

今天想在自己管理的一台机器(安装的 freebsd)上设置下防火墙,使用 ipfw 的时候,发现 ipfw 模块没有加载。

一时冲动就直接 kldload ipfw ,立刻就被防火墙踢了出来 :( 。无奈只好联系机房的同事帮忙按一下电源。

好吧,这次我知道 freebsd ipfw 默认的配置是有一条 65535 deny ip from any to any 的规则了。一加载 ipfw 模块根本不给我机会通过 ssh 远程添加新的规则。

以往都是在自己办公室的机器上折腾,没什么好担心的,第一次操作千里之外的机器的 firewall ,一不小心就傻眼了。

重起之后谨慎多了。还是改 /etc/rc.conf ,然后用 /etc/rc.d/ipfw start 的脚本启动好了。

保险起见,我添加了 firewall_enable="yes"firewall_type="open" ,并在机器的桌面机上测试了一下,感觉没有问题。就登陆上远程机器上操作。

可是当我输入 /etc/rc.d/ipfw start 后,又被踢了,真是欲哭无泪啊。终于有明白的同事告诉我,因为 ipfw 添加规则时会有标准输出。但是加载完 ipfw 模块后,新的规则没来的及加上前,我的连接就被断开了。后续的输出失败会导致系统发信号让程序退出,后续规则就没有加上了。

再次麻烦机房同事重启系统,一切正常。

这次算长了点经验。远程开启 ipfw ,一定要重定向标准输出,更安全点是把标准错误输出也一并重定向。

/etc/rc.d/ipfw start >/dev/null 2>&1

May 22, 2008

防止中间人攻击

去年,几个游戏组的同事给我描述了他们发现的针对游戏的中间人攻击(MITM attack)。有人做了一个代理服务,种植木马到用户机器上,使用户机器对游戏服务器的 tcp 连接都重定向到代理服务上。(这个步骤其实不一定在用户机器上做,网关和路由器上都理论上存在被做手脚的可能,这是用户自身无法保证的。

代理服务可以一直监听用户到服务器的通讯,直到认为认证过程结束,不再转发用户的数据,改由自己操作用户的帐号。这样就可以达到转移用户虚拟财产的目的。

今天,和几个同事讨论了这个问题和解决方案。

March 02, 2008

MMO 的排队系统

这两周那是忙的天昏地暗。都是些琐碎的事情,两个项目的。理理代码,发发邮件,打打电话,改改 bug ,开开会,签签字,写写报告。周末也加了一天班,工作居然是安装一个论坛系统,外加修改 css ,以及修改模板,调整版面。没办法,时间紧,人手少。

btw, 在服务器上装 php 时,因为开始 ports 没有更新,出了好多问题。mysql 一开始忘记装 gbk 的支持,困扰我老半天。鄙视一下公司购买的某著名 php 写的论坛系统,居然默认不是用 utf-8 编码的。

闲话扯到这里,今天想谈一下上月底,四年逢一次的好日子里,我们公司憋了好久的《天下2》终于又一次体验测试的事情。

January 28, 2008

安全的提交密码

这篇 blog 的内容原本是去年 10 月写的,当时正在看《24小时反恐》,脑子里涌现出无数古怪的想法,觉得这个世界到处都是特工,什么通讯手段都不可靠。在我们还没有能力获得量子加密需要的硬件前,能有限依靠的恐怕只有数学能保证的加密技术。

当时那篇 blog 写了一大篇,自己都觉得太过于天马行空的乱扯,就没有公开。

不过今天开周会,我们又提到游戏 client 提交密码的安全性问题,指派了一个同事最近在这方面做些工作,这里也写点以前研究的东西,留点记录吧。

简单说,我们应该避免在一次登陆过程中从互联网连接中传递明文的用户名密码信息。这是一个起码的要求,但是我们以往的产品做的并不好。很多时候都是伪加密。就是 client 用个私有算法将密码信息编码后传送,再由 server 用相同的算法还原。

这个安全性极度依赖 client 的程序不被逆向工程。一旦有人完全逆向工程后,只需要他监听到通讯,就可以还原出用户的密码。

物理上防止通讯被监听的技术在可见的时间内几乎不可能被大众使用,我们现在即使能保证自己的 client 机器没有被人动过手脚,还是无法知道自己的通讯数据是否被监听。只能寄希望于数学的加密技术了。

SSL 就是干这个的,但是由于种种原因,暂时我们还不能在游戏 client/server 中推行使用。那么,现在是不是可以抽出一些东西来,自己先在程序中实现出来用着。其实,优先要做到的,无非是安全的让用户提交用户名密码而已。

October 15, 2007

让游戏用户安全的登陆

我曾多次在 Blog 上讨论过增强游戏客户端的登陆安全问题。这个问题仅靠软件手段之所以难以从根本上解决,是因为现在国内的网络安全环境实在是太差,我们无法确保用户的机器上没有木马。

游戏客户端面临的问题更加严重。受经济利益趋势,几乎所有的热门游戏都有无数针对性木马泛滥。用户很难保证他每天运行的游戏客户端有没有被恶意程序修改过。(修改不仅包括直接对硬盘上的客户端程序做永久性修改,还包括使用内存黑客程序动态修改运行时代码)

我们几乎做不到让 client 程序自检,确保自己没被动过手脚。

周末又一次想到这个问题时,突然想到,其实最不容易被伪造的 client 软件其实就是最容易被动手脚的浏览器。虽然像 IE 这样存在很多安全问题的浏览器,会被无缘无故的插上诸多恶意插件。但不可否认,木马想骗过浏览器截取到其中的特定传输内容还是颇不容易的:大多数恶意插件都会很快被检查出来。毕竟这个是现在反木马病毒软件全力去做的事情。而且想伪造一个浏览器上的登陆过程比伪造一个游戏 Client 的登陆过程技术上要困难的多。

如果我们让游戏登陆时,启动浏览器,用 https 协议连接登陆服务器去通过身份认证。然后用服务器返回的 session key 来启动游戏 client 进入游戏。这个登陆过程可能更加安全。(由于 SSL 的技术保障,这个过程也可以防止 ARP 欺骗导致的密码把监听)

September 29, 2007

独立的游戏用户登陆认证

网易的所有产品都使用网易通行证系统做用户身份认证,包括游戏产品。

这是降低新产品用户门槛的好方法。几乎所有的网络服务提供商都弄了个自己的统一用户认证系统,网易通行证也是干的这挡子事,内部我们把这套系统简称为 URS。公司在 URS 系统上投入了很多人力和资源,但是其表现总是跟不上我们的需求。我个人从 03 年开始就不断的在提一些安全方面的改进建议。但是由于这些系统涉及面太广,想做出些实质性的改变举步维艰。

不断的写建议书,不断的参与北京 URS 部门的技术会议,让我充分理解了他们的困难,和其中许多非技术难点的难处。

但是,国内糟糕的网络安全环境,日益严重的游戏帐号失窃问题,让我不得不时常关注这方面的问题。最近,又想了个改进方案,希望可以有所改善安全问题。

July 05, 2007

游戏服务器组间的通讯

网络游戏世界的构建有越来越大的趋势,游戏设计者希望更多的人可以发生互动。技术人员的工作就是满足这些越来越 BT 的需求。

我们目前这个项目由于是自己主导设计,而我本人又是技术方案的设计者。所以,技术解决不了的问题就不能乱发牢骚了。作为游戏设计者,我希望整个游戏世界的参于者可以在一个唯一的大世界中生存,他们永远有发生互动的可能。注意这里只是保留这种可能性,实际上,即使是现实社会,每个人的社交圈子都不大。即使是千军万马的战场上,无论是将军还是士兵,都不需要直接跟太多人互动。

我们的游戏的技术解决方案仍旧是将游戏大世界分成若干独立服务器组,人为的将人群切分成更小的独立单位。这里,技术上需要解决的是:服务器组间可以灵活的交换数据。

March 30, 2007

游戏服务器内的组播

游戏服务器在设计时,会有许多组播的需求。比如一个 NPC 需要向周围的观察者播出它的状态信息。无出不在的各种聊天频道中需要向频道中的参于者广播聊天消息等。

通常,我们会为每个组播的请求维护一张列表,然后再把需要的信息包发送给指定列表上的多个连接。这个过程在很多地方都会遇到,一个设计的不太好的引擎,再这里有可能滋生诸多 bug 。尤其在多服务器的设计时尤其如此。

这两天,我试图寻找一种简洁统一的解决方案。

March 29, 2007

游戏服务器处理多个连接入口的方案

最近在考虑为一组游戏服务器配置多个连接入口。这个需求来至于我们的国情。作为大的游戏运营商,势必要考虑国内的网络状况——南北不通的现状。以往别的公司的代理游戏,由于不是自己开发,都选择了一个实际的方案:在北网通和南电信各放若干组服务器。北边来的在北边玩,南方住的安居在南方。

我们的游戏却不行,因为我需要一个完整的大世界,必须解决南北互通的问题。据我所知国内运营的游戏 EVE 是比较好解决了这个问题的。

我们自己的游戏大多也解决了,只是宣传上还是鼓励玩家登陆相应的服务器。我们的解决方案本质上很简单。建立有多个出口的机房,同时拥有电信和网通的线路。或是用自己的线路互联电信和网通的机器。这后者普通用户自己在家也可以做,只要你肯花钱,同时购买电信的 ADSL 于网通的宽带即可。目前许多城市两者都向大众提供服务。

当然,最终我们还是需要编写服务器的程序员做一些配合。

February 01, 2007

多服务器的用户身份认证方案

当游戏服务器群达到一定规模后,让用户只从一个入口连入会给这个入口带来很大的压力。这样,我们就需要让服务器群中的多台机器都允许用户直接连接。

当服务器开放给用户直接登陆后,必须面临的一个问题就是用户身份认证的问题。

大多数提供网络服务的公司都做了一套统一的用户认证系统,比如微软的 passport ,网易的通行证,等等。为了避免重复验证用户身份而给用户认证系统带来过大的负担,云风在这里给出一个参考解决方案。

March 01, 2006

利用 Cache 减少传输的数据量

今天研究 wow 时候发现,他 cache 了很多的信息。我们可以在 WDB 目录下找到这些 cache 文件。没有仔细去研究这些文件到底放了些什么,但是由此却想到一些东西。

我们在做 mmo 的服务器的时候,有些信息,数据量很大,却并非经常变动。比如物品的细节描述,工会/帮派信息,甚至还有好友列表等等。在 IM 软件中, cache 住朋友列表到本地硬盘是很常用的手法,不过我参与的几个 MMO 项目都没有这样做。

其实,这些不易变的信息,在 client 需要获取的时候,只需要在请求协议中加入一个自己 cache 的信息包的校验值过去就可以了。server 校对自己一方的校验值,当判断与 client 相同的时候,就不需要重发这些信息了。

很多信息都可以如法炮制,扩展开看,还可以是 npc 对话,任务描述等等。

January 20, 2006

基于TCP数据流的压缩

这两天研究了一下 lzw 压缩算法,据说它专利已经过期了,那么应该可以随便用了吧。

这种基于字典的压缩算法,一个很大的优势就是,如果数据流中经常出现同一个词,那么会被极大的压缩掉。重复性的信息在网络游戏中经常碰到,不过是基于包和包之间的,而不是同一个包之内的。游戏又跟别的应用不太一样,我们往往希望更快的把数据交到对方,减少网络的延迟,所以大多数情况下,我们不会积攒很多数据一起发送。所以,不能简单的以每个数据包为单位调用压缩器。

January 16, 2006

貌似合理的网络包协议

最近有个小项目,很快的开始,似乎也能很快的完工。就一个不大不小的游戏,2d 的,图象引擎是成熟的,然后我就这这段日子对 lua 的热情,用 lua 把原来写的 C++ 图象引擎做了个封装。用起来感觉良好,UI 部分封装的也不错。游戏逻辑用 lua 驱动貌似很方便。一度幻想着哪天把它开源出去,没准可以成为 lua 开发 2d 游戏的准标准。想想可能性不大,纯当意淫了。

小项目比较能锻炼队伍,所以我做为基本封装就跟新同事上课了。看着别人做程序心里痒痒的,做不出来急急的。恨不得什么都自己代劳。

其中一个同事的工作安排是网络协议的封装,争取用 lua 封装的好用一些。

October 21, 2005

针对网络游戏客户端更新的 P2P 网络

我最近在构思一个用 P2P 网络同步文件的计划。用于以后网络游戏的 client 同步更新。现有的 BT 之类的软件很成熟了,不过我考虑到网络游戏有其特殊性,说不定可以做的更好。

网络游戏可以说有一个天然的稳定的 p2p 网络的基础,好象我们的梦幻西游,最高已经超过八十万玩家同时在线了。而所有玩家所需求的是同一份 client。这跟 BT 网络上传输文件的需求和环境都不太相同。