« 大恩莫言谢 | 返回首页 | C 的回归 »

玩了一下 ajax

起因是这样的:

几个同事在棋牌群里聊天,说找不到搭档打桥牌。网上也没啥好地方去,大家都比较讨厌下客户端和注册。我说,不如我做一个免客户端免注册的桥牌网站吧。然后就开始了。

直觉告诉我,ajax 技术可以实现这些。但是我没做过 web 方面的开发,仅有的一些知识只在几年前写过一个 php 留言本。一开始觉得 ajax 这些时髦玩意学一下午,然后一通宵就可以把想要的东西做出来。哪知道,结果不务正业干了半周了,中间还熬了两晚上,到今天都没做完。明天要出差,只好放一放了。

做这件事情之前,我先回忆了从前道听途说以及自己臆测得到的一些不完整的知识片段:

web 开发跟我们现在做的胖客户端长连接的网络游戏很不一样。web 软件几乎都是基于 http 协议通讯的,http 是一个短连接,每次发送一个请求,然后接收一段回应的文本,如此而已。

为了让浏览器可以记住有前后关联的事件,我们可以选择把一个 session key 保存在浏览器 cookie 中,或者直接编码在 url 里,在服务器和浏览器之间不停的传递。但是浏览器又天生允许用户无次序的刷新各个页面,且可以并发许多请求。所以从某种简单来看,浏览器跟 web 服务器的通讯类似 UDP 的协议。它不保证请求的次序,不保证处理所有的请求,甚至允许同一份请求解接收多次。

用户在使用 web 服务的时候,从技术层面上看,只能他主动向服务器索取数据。服务器不能主动发数据过来。这在交互性强的软件中,是一个极大的障碍。ajax 网站解决这个问题的方案通常是设置一个定时器,有事没事就去问服务器,“您老有什么交代吗?有事早奏,无事退朝”。

为什么 ajax 程序可以不刷新页面就可以对用户的操作做出反应?(不操作也能有反应,也就是上面提到的定时器)远古的做法是在页面上放一个 iframe,把它的尺寸缩到你看不见,或者挪到不碍眼的地方。由主页面上的 javascript 控制这个 iframe 的刷新,自然提交的 url 也是由主页面的逻辑来做适当编码的。服务器接到这个 iframe 的刷新请求,就会把相应的回应发过来。这些信息通常很短小,信带到了就行,不需要人来读。主页面的 js 分析这些反馈结果,就可以做出适当的动作了。

后来时代发展了,不用再创建一个看不见的 iframe 了。浏览器允许直接创建一个用来发送 http 请求的对象—— XMLHttpRequest ,还可以方便的从中获得服务器返回的数据。做这样的事情就简单了很多。

以上,就是 ajax 中最重要的基础技术点了吧。


动手做这件事的时候,我选择了最为熟悉的开发工具,Lua 。早就听说有牛人做了一套基于 Lua 的 web 开发平台 Kepler ,连同 Lua 实现的一个一个轻量的 web server 一起,安装包才 700k 大点。在 Windows 和 Unix 下都可以一键安装。

算上下载时间,我花了大约五分钟时间把开发平台搭建好了。就着文档开始堆代码。做起来才发现,事情没这么简单。

第一夜遇到的第一个实质问题是关于整个系统的结构该怎么搭建:

打牌这种互动性比较强的软件,必须在服务器上有一个地方保存牌局。我不想牵扯到数据库,这可能使我一晚上把东西做完的希望泯灭(因为我对数据库不太熟,得花不只一个晚上学习)。web 服务器处理 http 请求都是一个个独立的。每次请求结束,整个环境就消失了。翻了下文档,发现 Kepler 提供了一个左右 session 的模块,似乎可以解决这个问题。文档到细节处就没有了,好在源码并不长,读了下就明白了。它提供了一个 lua 的 table 持久化的工具,可以方便的把 lua 的环境写到一个文件中。session 模块管理了一堆这些 session 文件。可以在你需要的时候把以前持久化的数据读回内存。

接下来就发现了严重的问题:session 的 load 和 save 方法是不加锁的!因为一个牌局至少有 4 个用户共享一个 session 。不加锁一定会导致数据混乱。一开始我想试图用版本号避免数据冲突的问题,最后发现实现的复杂度过高。最终放弃。到现在我还没想明白,kepler 提供的这个 session 模块,在没有锁支持的情况下,到底有什么实用价值?

不过 kepler 并非工作在真的多线程环境下,它利用了 lua 的 coroutine ,而 coroutine 的切换似乎之发生在数据内容生成的时候,即 cguilua.put 的调用时。如果在所有内容生成前,把 session 里的数据生成后,没有锁还是勉强可行的。否则,只能换用数据库或自己写一个带锁的 session 管理模块了。

总之,第一晚研究这些东西,并读了大段 kepler 的源代码,花掉了不少时间。我的计划表就这样延长了。

第二天跟同事讨论了一下,继续研究 kepler 的代码。发现它时间了一个叫做 rings 的东西,这个玩意可以从一个 master lua state 中创建出若干 slave state 出来。这个方案可以轻松避免处理每个 http 请求过程可能发生的资源泄露。这个道理跟我们用进程来管理 os 中的各个活动的软件一样,杀掉进程总可以把资源回收完全,而不需依赖软件的正确实现。

比较有趣的事,我们可以从 slave state 中向 master state 读写数据。由于 master state 长期存在,放在里面的数据就是持久性的。把牌局放在 master state 空间中非常方便,还可以避免每次浏览器过来的请求都需要重新做一次数据持久化的工作。

甚至,我们不需要把逻辑放在 slave state 中实现。第一次初始化的时候把逻辑代码灌到 master state 空中,以后就可以方便的调用了。

第二夜我又遇到了另几个小问题。

kepler 的 1.1 目前还是测试版,存在 bug 是必然的。可是让我很不巧的第一次用就碰到了,很是郁闷。

cgilua.redirect 这个 api 居然不能工作 :( 。我对 http 协议不熟,结果晚上现找资料读。发现kepler 自带的 web 服务器在发送重定向数据头的时候,没有正确的发送回应码 301 或 302 ,而是发送了 204 。起初我我只想读一下相关代码找到 bug 修正。没想到,这一读,就读了一通宵。

总的来说,kepler 构架的是非常巧妙的。可是越巧妙的构架,弯弯就绕的越多。为了解决 lua 5.0 和 5.1 之间的语言差异,又不至于影响效率。它居然用了一段代码去生成另一段用于运行时生成代码的代码…… 看明白后,那个寒啊。幸亏没被饶进去晕死掉。估计动态语言这种用法,只有用 C++ 中模板之模板才可以媲美了。

好在这不是大问题,如果不是我好奇心太重,也不至于花这么多时间在跟原计划不相关的代码阅读上了。

第二晚,还是没做出啥东西来。

好好睡了一觉后,感觉思路理清楚了。整个项目最麻烦的一点在于浏览器上的应用跟有专有客户端的软件不同。浏览器用户有可能中途关闭窗口,或是半路杀进来。又或者把自己的窗口复制成多个。除非你在这些情况发生时,野蛮的让用户重新登陆,重置所有的状态。不然就得好好考虑这个问题如何解决。

一种方案是,让服务器每次通讯都传递牌局的完整状态。另一种是,让服务器维护牌局的多个状态版本,而让客户端的浏览器每次请求发送他拥有的版本。然后服务器之需要通知版本差异即可。

这两个方案前者比较容易实现,后者可能可以节约许多带宽。我考虑了一下,采用了第一个方案,但是依旧维护一个版本号用来检测状态变化。

睡了一觉醒来后,精神不错。只用了几个小时就做好了游戏大厅和发牌的逻辑。看起来一切都挺正常。中间鄙视了一下 IE 。明明在 Opera 上都是正确的,IE 下那个定时触发的 http 请求就是不正确。看了 web 服务器这边的记录,发现是 IE 私自决定 cache 住结果了。而按标准,URL 里带 ? 的动态页面请求是不应该被浏览器 cache 的。google 了一下,发现很多人也遇到过这种情况。加了个数据头就可以解决。

接下来的工作就是体力活了。编写打牌的逻辑,记分,还有做出漂亮的界面等等。重新估算了一下,全部完成可能还需要几十个小时的人工 :(


关于这个东西,我的想法是:不需要用户注册,第一次登陆则由服务器生成一个唯一用户 id 记录在用户 cookie 中。用户可以登陆任意房间,也可以让服务器创建新房间出来。每个房间有一个唯一房间编号,若想约朋友一起打牌,只需要告诉他房间号即可(或者直接发送一个 URL)。

房间没有权限管理。每个人都可以坐在任意位置。对于桥牌来说,就是 5 个位置:东南西北和旁观。每个人进入房间,默认坐到旁观席上。而前四个位置只能坐一个人。如果这个人一直和服务器保持通讯,就没人可以赶他走。但一旦他超时,任何一个旁观者都可以顶替他的位置。

整个系统不需要做积分和排名(当然要做也很容易),甚至不需要任何数据库。服务器里所有数据都保存在内存中。没有任何权限的限制,每个来玩的人都是平等的。约朋友一起下棋打牌只需要发送一个房间的 URL ,不需要安装插件,甚至可以做一个非 ajax 版本来适应手机浏览器。

如果以后需要做用户管理,可以让用户输入他的 email 地址做用户 id 。大多数情况下不需要密码登陆,email 地址只是为了用户 id 的绝对唯一性。如果某些用户需要密码保护的话,再将密码发送到 email 中即可。


ps. lua 的描述能力还是很强的,如果有人怀疑 lua 在 web 开发上的潜力的话,可以看看 Sputnik 这个 wiki 系统 。整个系统号称只用了不到 2000 行 lua 代码。作者宣称会继续努力把代码精简到 1000 行之内。

Comments

请问你是怎么解决编码问题的!能把编码UFT8的发我看下么?
请问你是怎么解决编码问题的!能把编码UFT8的发我看下么?
还有下文吗?
吹吧.做出来再发表看法不迟.
4个用户共享session?我不懂你用的框架中的session,不过大部分服务器端框架都提供session和application,前者是一个客户端多次请求间共享的,但不能跨越不同客户端的请求共享;后者是整个应用程序中共享的,跨越任何两个请求。至于需要共享4个用户之间的数据,通常都要结合session与application。
我倒是曾经花过两天时间用flex builder 2做过一个免注册的在线象棋程序。ActionScript可以用长连接,很方便。
云风这样的写 server 的高手,不用 comet 技术真是屈才了。 使用 http 长连接,服务器保持住连接,并在需要的时候随时向客户端 push 数据。 客户端一边连着这个长连接,在需要向服务器发送消息的时候通过另外的普通的 http短连接 发给服务器。 这样以来基本上就跟写网游客户端差不多了 ;-) 而且客户端有很好的响应速度。
顺便一提,看了这篇文章Coders @ Work http://blog.csdn.net/g9yuayon/archive/2007/09/06/1774124.aspx#FeedBack 然后看了一下排名的链接,全是牛人啊,真是神往.而且John Carmack排在前10,unreal引擎的Tim Sweeney还有Michael Abrash也在前2、30位左右.
虽然基本不懂web应用这些东西,读读云风的思路也是不错的:)
嗯,根据以往云风对GOOGLE的态度,看来云风是在为加盟GOOGLE做准备,网易要小心哦! :-)
呵呵,隔行如隔山啊.
云风的动手能力真强...
可以试试actionscript
不错
小而精才是王道
作小东西,工具的熟练程度对工期影响相当大 :)

Post a comment

非这个主题相关的留言请到:留言本