玩了一下 ajax(轉)

轉自:雲風的 BLOG 

起因是這樣的:

幾個同事在棋牌羣裏聊天,說找不到搭檔打橋牌。網上也沒啥好地方去,大家都比較討厭下客戶端和註冊。我說,不如我做一個免客戶端免註冊的橋牌網站吧。然後就開始了。

直覺告訴我,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 行之內。

http://blog.codingnow.com/mt/mt-tb.cgi/293

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章