您现在的位置:首页
--> 云风的 BLOG
• 一个登陆认证系统
需要对接用户登陆认证系统。虽然已经有很多成熟的认证协议,比如最有名的 Kobas 。但这次时间紧迫,我就临时设计了一个简单协议。因为不是 web 应用接入,所以我不想直接使用 https 来提交用户名和密码,而基于 http 协议,在不安全信道上建立了一个自定义协议来应付一下。这种临时设计的协议当然不会很缜密,但也基本够用。
游戏开发和很多其他软件开发的一个巨大区别就是,你无法把程序得到正确结果当成任务的完成。运行时间往往成为重要的约束条件。如果一件事情在规定的时间片执行不完,代码实现的再正确都没有意义了。而局部的优化热点往往也意义不大。因为如果只是需要小幅度的提高性能,那么采购好一些的硬件就够了。一个模块的性能,要从数量级上的提高,必须重新思考需求,改变需求,重定义我们要做什么。
• 位置同步策略
我们需要简化问题,并先解决一个比较小的问题集。把系统搭建起来,这样可以迭代测试。自己玩过一些,才好改进。所谓,快速原型,吃自己的狗粮。那么现阶段集中做不同玩家的位置同步。仅解决这一个问题。把 3d 客户端和服务器搭起来,可以真正的跑起来。我们的 IT ,Aply 同学已经在内网搭建了模拟环境,模拟各种糟糕的网络环境。测试恶劣环境下的表现,我们有比当年更逼真的工具来实现。
所谓 AOI ( Area Of Interest ) ,大致有两个用途。 一则是解决 NPC 的 AI 事件触发问题。游戏场景中有众多的 NPC ,比 PC 大致要多一个数量级。NPC 的 AI 触发条件往往是和其它 NPC 或 PC 距离接近。如果没有 AOI 模块,每个 NPC 都需要遍历场景中其它对象,判断与之距离。这个检索量是非常巨大的(复杂度 O(N*N) )。一般我们会设计一个 AOI 模块,统一处理,并优化比较次数,当两个对象距离接近时,以消息的形式通知它们。 二则用于减少向 PC 发送的同步消息数量。把离 PC 较远的物体状态变化的消息过滤掉。PC 身上可以带一个附近对象列表,由 AOI 消息来增减这个列表的内容。 在服务器上,我们一般推荐把 AOI 模块做成一个独立服务 。
经过一个月, 我基本完成了 skynet 的 C 版本的编写。中间又反复重构了几个模块,精简下来的代码并不多:只有六千余行 C 代码,以及一千多 Lua 代码。虽然部分代码写的比较匆促,但我觉得还是基本符合我的质量要求的。Bug 虽不可避免,但这样小篇幅的项目,应该足够清晰方便修正了吧。 花在 Github 上的这个开源项目上的实际开发实现远小于一个月。我的大部分时间花了和过去大半年的 Erlang 框架的兼容,以及移植那些不兼容代码和重写曾经用 Erlang 写的服务模块上面了。这些和我们的实际游戏相关,所以就没有开源了。况且,把多出这个几倍的相关代码堆砌出来,未必能增加这个开源项目的正面意义。感兴趣的同学会迷失在那些并不重要,且有许多接口受限于历史的糟糕设计中。 在整合完我们自己项目的老代码后,确定移植无误,我又动手修改了 skynet 的部分底层设计。
今天发现 Skynet 消息处理的一个 bug ,是由多线程并发引起的。又一次觉得完全把多线程程序写对是件很不容易的事。我这方面经验还是不太够,特记录一下,备日后回顾。 Skynet 的消息分发是这样做的: 所有的服务对象叫做 ctx ,是一个 C 结构。每个 ctx 拥有一个唯一的 handle 是一个整数。 每个 ctx 有一个私有的消息队列 mq ,当一个本地消息产生时,消息内记录的是接收者的 handle ,skynet 利用 handle 查到 ctx ,并把消息压入 ctx 的 mq 。 ctx 可以被 skynet 清除。为了可以安全的清除,这里对 ctx 做了线程安全的引用计数。每次从 handle 获取对应的 ctx 时,都会对其计数加一,保证不会在操作 ctx 时,没有人释放 ctx 对象。
最近我的工作都围绕 skynet 的开发展开。 因为这个项目是继承的 Erlang 老版本的设计来重新用 C 编写的。 再一些接口定义上也存在一些历史遗留问题. 我需要尽量兼容老版本, 这样才能把上层代码较容易的迁移过来。 最近的开发已经涉及具体业务流程了, 搬迁了不少老代码过来。 我不想污染放在外面的开源版本。 所以在开发机上同时维护了两个分支, 同时对应到 github 的公开仓库, 以及我们项目的开发仓库。 btw, 我想把自己的开发机上一个分支版本对应到办公室仓库的 master 分支, 遇到了许多麻烦。 应该是我对 git 的工作流不熟悉导致的。我的开发机的 master 对应着 github 上的 master , 但我大多数时间在一个叫 ejoy 的分支上开发。 这个分支对应到办公室服务器的 master 分支上。
今天把 Skynet 的集群部分,以及 RPC 协议设计实现完。 先谈谈集群的设计。 最终,我们希望整个 skynet 系统可以部署到多台物理机上。这样,单进程的 skynet 节点是不够满足需求的。我希望 skynet 单节点是围绕单进程运作的,这样服务间才可以以接近零成本的交换数据。这样,进程和进程间(通常部署到不同的物理机上)通讯就做成一个比较外围的设置就好了。 为了定位方便,我希望整个系统里,所有服务节点都有唯一 id 。那么最简单的方案就是限制有限的机器数量、同时设置中心服务器来协调。我用 32bit 的 id 来标识 skynet 上的服务节点。其中高 8 位是机器标识,低 24 位是同一台机器上的服务节点 id 。我们用简单的判断算法就可以知道一个 id 是远程 id 还是本地 id (只需要比较高 8 位就可以了)。
当我们在修改数据结构中某个副本时,为了修改过程的原子性,我们需要复制一个副本出来,修改,然后利用 CAS 交换到主干上。这个过程中,其它读线程,可能引用老的版本,读完后就需要销毁掉过期的版本。在有 GC 机制的语言中这非常简单。但是在 C/C++ 这种手动管理内存的条件下,几乎变得不可能。对,我们可以用引用计数来管理。但难点在于引用记数本身需要放在对象上,那么改写引用值却需要获得对象本身先,这个变成了绕不过去的死结。在并发条件下,如果你不使用锁,那么获得对象指针后,到操作引用记数之间,无法确保对象不在那一刻被其它线程减少引用而销毁掉。
• 原子字典
一个需求:一个玩家数据的写入者,可以批量修改他的属性。但是,同时可能有其他线程在读这个玩家的数据(通过共享内存)。这可能造成,读方得到了不完整的数据。 我们可以不在乎读方得到某个时间的旧数据,但不可以读到一份不完整的版本。就是说,对玩家数据的修改,需要成组的修改,每组修改必须是原子的。 起先,我想用读写锁来解决这个问题。方案想好了,一直没有实现。只是把读写锁的基本功能实现了。 这几天这个问题被重提出来。因为,前段我们都采用了鸵鸟政策,当问题不存在(事实上我们也没有发现实际中出现可观测到的问题)。 反正探讨了好几个解决方案,一开始都是围绕怎么加锁,锁的粒度有多大来展开的。甚至,我们把其中的一种方案都实现出来了,并写了压力测试程序测试。不过,这些方案都不太令人满意。
• 无锁消息队列
状态同步,简单说就是一个玩家的 Agent 在做一个动作时,它需要把这个行为通知所有在虚拟场景中他附近的玩家。当很多人(超过 50 人)在一起时,就有大量的数据包需要广播出去。我们目前的做法是基于这样一个假设:服务器内部数据包的传递非常廉价。广播包比逐个发送更加廉价,这是因为,单机内部广播,可以避免大量的数据复制。所以,在同一张地图上,我们会简单的把任意一个 Agent 的状态改变信息广播给同张地图的所有其他人。这样就不需要动态维护分组信息。当每个 Agent 收到广播包后,再根据自身的逻辑进行过滤,再发给对应的客户端。
在做策划表格解析的时候,我们希望可以在表格里直接填写一些脚本代码。我们的脚本语言使用的 Lua ,所以,直接填写 Lua 代码最为简单。但是,策划同学强烈需要在脚本中直接使用中文。而 Lua 原生并不支持使用中文作为变量名。一开始我们使用了一些变通的方案:比如建立一个字典,把中文词通过程序替换成相应的拼音。倒也能工作。 昨天在午饭途中的电梯里,我想到了另一个方案,用了一个下午实现出来验证可用。 修改 Lua 的语法解析代码,让其支持汉字并非难事。但我不太想通过给 Lua 打补丁,修改 Lua 语言的方式来做这件事情。即,我不想因为这个项目为 Lua 创造一门方言。但是,我们却可以把策划表格中填写的代码当成一种 DSL ,正如之前我实现的公式解析 那样。把这部分用 Lua 的方言来实现,把修改的影响减少到最小,而不蔓延到整个系统的实现语言中去,或许是个更好的方法。
这几天我的工作是设计未来游戏服务器的热更新系统。 这部分的工作,我曾经在过去的一个项目中尝试过 。这些工作,在当时一段时间与广州网易其他项目交流时,也对网易其他项目的设计产生过一些影响,之后,也在实战中,各个项目组逐步发展出许多热更新的系统来。 我最近对之前所用到的一些方案,如修改 lua module 的加载策略,增加一些间接层,来达到热更新代码的系统设计做了一些思考。感觉在处理热更新这个问题时,还不够严谨。经过两天的思考,我按我的构思实现了新系统的雏形。 在函数式编程语言中,热更新通常比较容易实现。erlang , lisp 都把热升级做为核心特性之一。函数副作用越小的语言,越容易做热升级:你只需要简单的把新写的函数替换回去就好了。
如果你在同一个进程里有多个 lua state , 它们需要共享大量的只读数据, 那么可能就不希望在每个 state 启动的时候都加载和解析一遍这些数据. 所以我们需要一个共享只读数据的方法。 前段时间,我实现了一个 共享内存服务 ,这个可以保证共享内存的安全读写。不过,如果数据是只读的,那么就不需要这么复杂了。 我们只需要把数据加载到一个 lua state 中,其它的同一进程内的 state 通过 C 接口去读数据就可以了。 今天,我做了简单的实现,放在了 github 上。目前可以支持 nil number boolean function table 的数据交换。 function 交换有一些限制,不可以绑定 upvalue 。是用 string.dump 和 load 实现的。 table 类型返回的其实是一组 key ,需要继续用 get 来读取数据。
Lua 只支持一种 number ,默认是 double 类型。虽然你可以通过修改 luaconf.h 里的定义,把 lua number 改成 int64 。但是为了 int64 类型而放弃浮点数,恐怕不是大多数人想要的。 int64 通常用在 uuid 上,也就是说不需要对其数学运算,只需要可以比较就好了。我以前最喜欢的做法是用 8 bytes 长的 string 来表示一个 int64 。这样,即可以做唯一的 key 用,又不用做复杂的扩展。 在 pbc 的 lua binding 库 中,对 fixed64 类型,我就是这样处理的。今天遇到新的需求,有同学希望可以在项目中直接处理 64bit 的 timestamp 。
前段时间谈到了 ringbuffer 在网络通讯中的应用 。有不少朋友写 email 和我探讨其实现细节。 清明节放假,在家闲着无聊,就实现了一个试试。虽然写起来还是挺繁杂的,好在复杂度还在我的可控范围内,基本上也算是完成了。 设想这样一个需求:程序 bind 并listen 一个端口,然后需要处理连接到这个端口上的所有 TCP 连接。当每个连接上要数据过来时,收取这些数据,识别出封包,发送给对应的逻辑层处理。如果数据不完整,则暂时挂起这些数据,直到数据收取完整再行处理。我写的这个小模块实现了这样一组特性,因为使用了唯一的 ringbuffer 缓存所有的连接,可以保证在程序运行过程中,完全没有额外的内存分配操作。
地图服务的另一项工作是管理所有的 NPC 和怪。我原本希望把单个 NPC 和怪都放到独立的进程(非 os 进程)中,以 agent 的独立地位存在。做过一番估算后,觉得内存开销方面不太现实。所以还是打算由地图统一管理它们。初步的计划是讲怪物的巡逻路径单独管理,把 AI 的状态机则独立出来。怪物是不作为观察者进入 AOI 的。让 agent 在观察到怪后,主动向怪发送消息激活怪的 AI 模块。这样可以在没有人活动的场景上,大大减少怪物 AI 轮询造成的 CPU 压力。这部分的实现以后由新入职的 mike 同学负责实现。这几天我们讨论了好长时间的相关方案。在实现过程中一定还会有许多实现者自己的想法。不过我相信 mike 同学多年的 MMO 制作经验可以很好的解决掉问题。
虽然现在 twitter google+ facebook (你也可以把前面的产品换成新浪微博,人人)已经成为网上公众信息交流的主流工具了。但论坛这一形式始终有它存在的价值。至少,在 mailling list 无法成为主流的状态下,产品在网上发布,大多还是需要一个类似论坛的形式为用户提供服务的。当然,google groups 本质上是一个邮件列表,它也把自己称为“网上论坛”的。我说的这个东西,应该大体上归类于 forum 。但 forum 这个词大多数中国人拼不清楚,大家更习惯称之为 bbs (我知道 forum 和 bbs 其实是有差别的)。 当年 ROR 正火爆的时候,有人说用 ROR 搭建一个网站只需要几行代码,没有更简单的了。有人回,不,用 Discuz 搭建一个论坛更简单。 以为然。 但是我始终不喜欢 Discuz 形式的论坛,尤其是它之后的发展。过于花哨繁杂了。我更喜
这是一篇命题作文,源于今天在微薄上的一系列讨(好吧,也可以说是吵架)。其实方案没有太多好坏,就看你信不信这样做能好一些或坏一些。那么,整理成 blog 写出,也就是供大家开拓思路了。 我理解的需求来源于网络服务提供程序的一个普遍场景:一个服务器程序可能会收到多个客户端的网络数据流,在每个数据流上实际上有多个独立的数据包,只有一个数据包接收完整了才能做进一步的处理。如果在一个网络连接上数据包并不完整,就需要暂时缓存住尚未接收完的数据包。 问题是:如何管理这些缓冲区比较简洁明了,且性能高效。 其实这个有许多解决方案,比如为每个网络连接开一个单独的固定长度的 buffer 。或是用 memory pool 等改善内存使用率以及动态内存分配释放,等等。今天在微薄上吵架也正是在于这些方案细节上,到底好与不好,性能到底如何。既然单开一篇 blog 了,我不像再谈任何有争议的细节,仅仅说说,用 R
由于在筹备做新的项目,所以关于网络游戏方面的讨论,在朋友圈子中多了许多。前几天和投资人吃饭,说起前几年盛大提出所谓免费游戏的策略转型,问道有没有可能在未来几年出现新的收费模式。从这个话题引开,饭桌上我跟叮当讨论了很久。 回想那一年,所谓免费游戏之风逐步侵蚀中国网络游戏市场之时,网易的开发团队起初是有所抵触的。我刚到杭州不久,远离广州的开发团队,静心思考了许多。所谓公平的游戏,我想我比大多数人都在乎。把玩游戏变成一个金钱投入的游戏,我是万般不愿意的。但我相信道具收费的方式有它内在的逻辑。实际上,在网易成熟的时间收费游戏,梦幻西游中,点卡交易系统早就存在,并良性运作了很久了。许多玩家已然在游戏中花掉了远远超过时间收费收取的金钱。 那段日子,记得 wding 和我讨论过道具收费的问题。我想了很多谈了很多。后来,wding 希望我回一趟广州,把我的想法和广州的策划同事们分享一下。我便去了趟广州
近3天十大热文
- [2527] 代理的加密部分
- [1325] 创业笔记 | 从0到1开公司是什么体验
- [646] vimgtd-在vim(gvim)中实现GT
- [569] 查找第K小的元素
- [70] Oracle MTS模式下 进程地址与会话信
- [64] 【社会化设计】自我(self)部分――欢迎区
- [63] Go Reflect 性能
- [61] 如何拿下简短的域名
- [60] android 开发入门
- [60] 图书馆的世界纪录
赞助商广告