2012年3月29日 星期四

使用 Node.js 控制網路連線管理員

Standard
既然以『國內首屈一指的 JavaScript 專家』為目標,使用 JavaScript 打造作業系統就是我們的終極目標。未來會將先前的 Flat Project(可參考舊文 Flat Project Demo - An OS for Tablets 平板作業系統Flat Project - 從山寨做起,親手打造炫麗的平板系統)以 JavaScript 重新架構,實作並提供一個更易於開發應用的環境。當然,就像過去所說,無論有多困難,產品化一直是我們的目標。

既然要開發作業系統,就要讓 JavaScript 能直接觸碰到系統層級或硬體裝置的控制,我們必須實作各種 JavaScript API 去達成這個目的,而前些日子所提及的『node-dbus』和『jsdx-toolkit』就是在做這類的工作。現在,基於 node-dbus,我們進一步和『connman(Connection Manager)』連接,實作出『jsdx-connman』,提供 JavaScript 網路管理機制的 API。這代表我們可以藉由 jsdx-connman 去控制無線、有線網路,甚至是WiMax、藍芽等各種連線介面。

如果想要嘗試 jsdx-connman,可以直接使用 NPM 安裝:
npm install jsdx-connman

這邊有個範例,讓我們可以使用 jsdx-connman API 去得知無線裝置的狀態和掃描當前環境的 Wifi 無線基地台:
var ConnMan = require('jsdx-connman');

var connman = new ConnMan();
connman.init(function() {
 if (connman.Wifi.Powered)
  console.log('Wifi is powered');
 else
  console.log('Wifi is not powered');

 if (connman.Wifi.Connected)
  console.log('Wifi is connected');
 else
  console.log('Wifi is not connected');

 console.log('Scanning Access Point...');
 connman.Wifi.Scan(function() {
  connman.Wifi.ListAPs(function(list) {
   console.log('Got ' + list.length + ' Access Point(s)');
   for (var index in list) {
    var ap = list[index];
    if (ap.Name)
     console.log('[' + ap.Name + ']');
    else
     console.log('[*hidden*]');

    console.log('Strength: ' + ap.Strength + '%');
    console.log('Security: ' + ap.Security);
    console.log('');
   }
  });
 });
});


後記

身為國內的小軟體公司,我們沒有太多的資源,也沒有太多的設備和環境。所以開發出來的東西不可能盡善盡美,更不可能在所有機器上都能運作良好。如果運作上有遭遇到任何問題,歡迎回報。:-)

2012年3月26日 星期一

這是兩碼子事!『犛清問題』與『不是我的問題』!

Standard
前一陣子有人寫了文章,以硬體工程師的角度,跳出來罵軟體工程師,因而吵得沸沸揚揚,在各社群網站上也被人重覆轉貼了好一段時間,掀起不少戰文。雖然整篇文章看下來情緒化字眼相當多,在某些部份也有些偏頗甚至太超過。但是不可否認,他的確有提到一個長久以來,許多工程師的通病,無論是軟體工程師還是硬體工程師。

身為工程師或與工程師一同工作的人,應該常聽到或自己本身常說這句話:『不是我的問題。』,就是這經典名句,總是搞得老闆慌、主管氣、工程師們更怒。而對這句話,我特別有感觸,因為過去我身為一個科技業救火員,常跳入火坑滅火,發現很多時候,就是這句話讓什麼問題都解決不了,導致起了大火,又沒人要解決,最後還需要我這外人進去犛清問題。

而在這些救火經歷中,我深刻體會到,證明『不是我的問題』是非常困難的。

一方面,有原罪加身,別人會把你當做在推卸責任。另一方面,因為非常難用『列舉法』去證明自己沒錯,又加上當局者迷,所以,才會有這類『列舉不全』的自我辯解:『我的程式在我的機器上測一點都正常,所以沒問題』、『我測量的電壓和波形都很正常,所以不是我硬體的問題』。其癥結在於,你有在其他電腦環境上測過嗎?你有測過所有工作狀況之下的電壓和波形嗎?大家似乎都忘了高中數學就學過,要找出『向量』以指出方向,要有兩個或更多個點嗎?就算你要用列舉法證明問題不在於你,你也要取樣更多更完整的證據。

所以,很多工程師想盡辦法證明自己沒錯,以為自己是在犛清問題,根本不然。因為『犛清問題』與『不是我的問題』是兩碼子事啊!

我認為,如果你要犛清問題,又證明自己沒錯,『證明是別人的錯』才是最好的方法,並且有助於專案前進和實質意義。

這邊有個自己團隊最近遭遇的小故事與大家分享:
我們與硬體廠 X 合作開發一個產品,其中發生了一個問題,播放影片時常會鈍鈍卡卡的,此外,因為該產品有觸控面板,如果影片播放中,用手去碰觸觸控面板,影片會立即變很鈍。

由於是 ARM 平台,播放影片都藉助晶片解碼(Decode),所以起初是找晶片供應商 Y 來查明真相,但是,晶片商『並不認為是自己的問題』,他們認定自己已經出了不少貨,平台夠穩定,所以應該也不可能會有問題。此外,因為觸控面板也會影響影片播放,所以供應商 Y 認定,問題肯定出在 X 廠商因自己動了手腳,加了其他模組或軟體所導致。所以,廠商 X 只好摸摸鼻子,請自己的 RD 找出問題。當然,X 廠商的軟體工程師也不認為是自己的問題,而硬體的人則測了老半天,也不覺得是自己的問題。互相推拖拉的下場,就是最終還是找不出問題所在。

而事實上,這個嚴重問題從去年底我們還沒合作時,他們就已經發現了,直到不久前都還沒解決,時間最少拖了一季。

重點是,既然都不是大家的問題,那問題到底是誰的呢?

為了解決這個問題,最近有個軟體工程師 A 提議,請硬體工程師 B 花幾個小時幫忙,盡可能把所有與影片播放不相干的線路都手動跳線『閹割掉』,想藉由除去硬體設計,反過來證明可能是工程師 B 的問題。而 B 為了真正徹底去掉所有干擾的因素,甚至連各類 Input 裝置(Key button, Touch panel)也都不放過。如此一來,根本沒有操作介面可以控制軟體,讓 A 必須自己想辦法改軟體,去掉一切軟體干擾的因素,使機器啟動後自動只播影片做測試。此外,為了保險起見,A 還特地去要了一塊公板再做一次做同樣的測試。

經過 A B 兩人的共同合作(或是你可以說是互相證明)後,所得到的結果是:『影片播放依然會鈍。』,所以可以推論問題肯定是出在驅動程式或是晶片上。

這時當所有證據指向同一個人,晶片供應商 Y 才願意承認是自己的問題,願意去試著找出問題。然後在深入了解後,才發現是驅動程式給錯了。而 X 廠商因此當了好多個月的冤大頭。

從這個小故事可以知道,軟體工程師 A 想出方法要證明是 B 的問題,而 B 不但做得相當徹底,也不給軟體後路,讓軟體必需修改成單純的自動播放。A 與 B 相互證明是對方的問題,結果得到了真正的解答。其中過程,雖然晶片供應商 Y,一直不肯承認錯誤,但被 A B 兩人共同證明並指出後,就不得不低頭。

奇妙的是,最後你會發現,當有爭議的雙方,都共同想證明是對方的問題時,兩方居然會提出同樣類似的意見和方法。其實,大家都知道怎麼去找出問題並解決,只是都不願跨出那一步而已。

後記

說來慚愧,過去我自己也常說:『不是我的問題』,也常看到很多自認聰潁的工程師,總是可以講的一口頭頭是道,撇清自己的問題。可是,一旦發生問題時,這些置身事外的人,都不能幫助專案進展,一點都比不上那些看似笨笨不高明,但努力去證明和犛清問題的工程師們。

最後,願大家都能成為一個用『證明是別人問題』來證明自己清白的工程師。共勉之。

2012年3月23日 星期五

能處理 Binary Data 的 Node.js Buffer Class

Standard
從一開始 JavaScript 就被設計成易於處理 Unicode 的語言,其所有的動作,都無法直接操作 Binary 類型的資料。也就是說,JavaScript 對處裡一個個位元組(byte)的資料是沒輒的,因此使用範圍大幅受限。為了解決這個問題,Node.js另外設計了一個 Buffer class,讓開發者可以處理 Binary data。

為了方便理解,其實我們可以將 buffer class 看做為 C 語言中的 malloc(),使用它就像是和直接系統要一塊原始記憶體來使用。此外,因為 buffer class 被設計成在 V8 heap 之外配置記憶體,因此不受限於 V8 Engine heap 的 1.6GB 大小限制。

你可以用下列三種方式建立 Buffer 物件:
/* 建立 16 Bytes 的記憶體空間 */
var buffer = new Buffer(16);

/* 直接代入資料陣列 */
var buffer = new Buffer([ 8, 8, 6, 9, 2, 6, 3, 3, 3, 5, 7, 2, 1, 1, 1 ]);

/* 直接代入字串 */
var buffer = new Buffer('String!', 'utf-8');
var buffer = new Buffer('String!', 'ascii');

你可以寫入資料:
var buffer = new Buffer(32);

/* 寫入一段字串 */
buffer.write('Write something', 'utf-8');

/* 只寫入兩個字元 */
buffer.write('Write something', 2, 'ascii');

以操作陣列(Array)的方法,單獨存取每個 Byte 的資料:
var buffer = new Buffer(32);

/* 將每個 byte 歸零 */
for (var i; i < buffer.length; i++) {
    buffer[i] = 0;
}

當然也有像 memcpy() 的資料複製:
var buffer1 = new Buffer('Source!');
var buffer2 = new Buffer(32);

/* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd]) */
buffer1.copy(buffer2, 0, 0, 6);


後記

更多的功能,可以參考官方的 API 文件: http://nodejs.org/api/buffer.html

2012年3月22日 星期四

讓我們用 Node.js 與 DBus 打交道

Standard
一個多工的作業系統,最不可或缺的機制就是 IPC,應用程式互相溝通的管道。但在比較複雜的桌面應用,系統核心本身的 IPC 就顯得太過簡單,不敷使用。而『DBus』是一個被廣泛使用的 IPC 機制,擁有權限控管、標準的 Interface 甚至是可跨網路溝通等特性,讓應用程式只要遵循標準的資料結構,就可以將資訊傳遞到另一支程式手上。DBus 尤其是在桌面環境下被使用最多,從網路管理員(Network Manager)、藍芽(Bluetooth)連線管理、亮度/音量變更等各式更新訊息通知(Notification)機制,無一不用到他。但由於 DBus 有眾多複雜功能,撰寫程式去使用他其實不是這麼容易,所以若是能用 JavaScript 來做這項工作,肯定能加快許多。

對 JavaScript 而言,打通了 DBus,意謂著可以控制網路管理員,去建立所有類型的網路連線(無論是有線、無線、需要撥號或 VPN),亦可以連接 iBus(當前主流的輸入法框架)以支援並控制輸入法,甚至是可以控制絕大多數的系統服務,說是打通任督二脈可是一點也不為過。

上網尋找,其實已經有一些人嘗試在為 Node.js/V8 Engine 寫 DBus 的支援,只是多半都不夠完整或 API 的使用上過於複雜,這讓筆者萌生了要自己開發一個新的 Dbus 模組的念頭。但重新開發實在是太累人,於是在網路上眾多的現成品中,挑選了一個 API 比較易於使用的專案『node-dbus』,然後為他加上更完整的功能,目前已經小有成果,除了提交回 upstream 外,也已經發佈到 NPM 上。

如果想要使用,你可以直接以 NPM 安裝(註:NPM上註冊的名稱和github上不太一樣):
$ npm install dbus

其使用上相當簡單,建立一個自己的 DBus Service 範例如下:
var dbus = require("dbus");

dbus.start(function() {

 var service_path = 'org.freedesktop.DBus.TestSuitePythonService';
 var object_path = '/org/freedesktop/DBus/TestSuitePythonObject';
 var interface_path = 'org.freedesktop.DBus.TestSuiteInterface';

 session = dbus.session_bus();

 var dbusRegister = new dbus.DBusRegister(dbus, session);

 /* Export Methods */
 var Methods = {
  WhoAreYou: function () {
   return 'I AM Spiderman!';
  }
 };

 /* Register a Bus Name */
 dbus.requestName(session, service_path);

 /* Register Methods */
 dbusRegister.addMethods(object_path, interface_path, Methods);
 dbusRegister.addMethods(object_path, 'org.freedesktop.DBus.Introspectable', {
  Introspect: function() {
   return '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>' +
    '<node name=\"' + object_path + '\">' +
    '<interface name=\"' + interface_path + '\">' +
    '<method name=\"WhoAreYou\">' +
    '<arg name=\"data\" type=\"s\" direction=\"out\" />' +
    '</method>' + 
    '</interface>' +
    '<interface name=\"org.freedesktop.DBus.Introspectable\">' +
    '<method name=\"Introspect\">' +
    '<arg name=\"data\" type=\"s\" direction=\"out\" />' +
    '</method>' + 
    '</interface>' +
    '</node>';
  }
 });

 /* Loop to waiting for somebody to call */
 dbus.runListener();
});

寫一支 Client 程式,透過 DBus 去使用前一支程式所提供的 WhoAreYou() 函式:
var dbus = require("dbus");

dbus.start(function() {

 session = dbus.session_bus();

 interface = dbus.get_interface(session, 
  "org.freedesktop.DBus.TestSuitePythonService", 
  "/org/freedesktop/DBus/TestSuitePythonObject", 
  "org.freedesktop.DBus.TestSuiteInterface");

 console.log(interface.WhoAreYou());
});

相較於用 C 寫 DBus 程式,這樣應該更簡單吧。當然,還有更簡化的空間,會在後續版本中改進。:-)

後記

最近一直有人問我,花這麼多時間去開發這麼多模組,只為了銜接各種 Library,那為何不直接使用 gir?

我的回答是,JavaScript 有自己的特性和慣用邏輯,如果只是將各個原本用 C/C++ 語言習慣所設計出來的 API 引入到 JavaScript,那和直接寫 C/C++ 語言去使用那些 Library,在開發難度上有何不同?雖然可以讓 JavaScript 得到很多支援,但對實際應用上卻沒有太大幫助。別忘了,使用 JavaScript 就是要享受無痛開發的樂趣。:-D

2012年3月20日 星期二

我為什麼大舉投入 JavaScript 的相關開發

Standard
和我比較熟的人,最近應該都知道,目前我專注於 JavaScript 和 Google V8 engine 的相關研究上,甚至決定從今年開始,公司營運以及技術團隊的方向,是企圖提供『最專業的 Node.js 和 JavaScript 相關服務』。(所以,如果您有需要,請聯絡我們 :-) )我非常清楚知道,有些人聽到 JavaScript 就歡天喜地,而有些人則不將他當一回事。更有多到數不清的技術人員,熟悉並使用 JavaScript 非常多年,聽到我說要提供『最專業的 JavaScript 相關服務』,會不屑一顧甚至以為我在瘋言瘋語。不過,如果你願意聽,接下來我會說明這是怎麼樣的瘋狂服務。

我不能阻止你對 JavaScript 有預設立場,因為這是無奈的歷史包袱,但請暫時放下過去成見,聽我靡靡道來。


我所遭遇的矛盾

過去,絕大多數人都只將 JavaScript 視為 Web 開發的一環,在大家的印象和認知中,JavaScript 充其量只是一個在瀏覽器中控制著 HTML DOM 的腳本語言。雖然近年來出現了 Node.js,過去微軟也支援使用者使用 JavaScript 撰寫 ASP 和系統腳本程式,但也不改這樣的看法:『JavaScript 是身處於末端的語言,與網頁、網站密不可分的東西』。所以,對許多硬底子的企業和研發人員來說,JavaScript 象徵了 Web/HTML 與不彰的效能,一種幻想的存在;而對很多 Web 開發者和前端工程師來說,JavaScript 卻是一種當今資訊產業的萬靈丹。如此天差地遠的矛盾,一直存在於這個產業界。

曾經當過 Web 開發者一段不少的時間,我承認也理解,使用 Web 技術來實作各類使用者界面(User Interface)和資料庫應用,非常容易和快速,不用考慮到太多的事。這一切要歸功於 JavaScript 和 HTML 的合作無間。所以,不時能聽到一些 Web 開發者,嘲笑作業系統程式開發者和嵌入式裝置的技術人員的辛苦,認為許多功能和特效,使用 Web 技術一下就達成了。

由於我過去也是『慣C』一族,做過很多包羅萬象的硬碰硬開發,小從一般電腦到嵌入式裝置、桌面系統到手機平版,大到各類特殊需求的系統研發,都有所涉獵。所以,對於系統資源和效能,一直有著莫明的堅持。也因為長期的磨練,深知所有的軟體開發,都在找尋與硬體拉距戰的平衡點。如何在有限的系統資源上,呈現最好的效果,一直都是我所在追求的事。

這兩種極端的軟體開發,開發效率和執行效能互為優缺點,讓兩方人馬各持己見。但因為同時身為這兩種人,產業界的矛盾,卻直接在我心中打架。


尋求同時兼顧開發效率和軟體效能的解決方案

其實一直以來,非常希望尋找一個語言,能快速又無痛的去開發各種應用,又不失效能、彈性和硬體、系統的耦合度。我知道,這無疑是天方夜談,但是若是一種語言沒辦法達成,如果用兩種語言配合,只要能達到這需求,又有何不可?

因此,Node.js 和 Libjs 這類的開放源始碼專案,給我很大的啟發。藉由輕量化,分離 JavaScript Engine 和其效能最大瓶頸 - 『瀏覽器』,使 JavaScript 非常適用於各種領域。許多非關效能的事,可以交給 JavaScript 來做,而需要效能、與硬體和系統溝通的工作,可以交給 C/C++ 來處理。兩者只要之間只要定好模組規範,即可兼顧開發效率和彈性及效能。JavaScript 因此可得到以往完全無法想像的發展空間,又能避免使用 C/C++ 辛苦的開發過程,加速程式應用的研發。

如舊文『我如何看待 NodeJS』所提及:『JavaScript 已經可以和 Python 相提並論』,基本上,我們已經可以用 JavaScript 開發各式應用,類似的模組機制已經與 Python 趨於相同,但所佔的系統資源則相對少很多,效能甚至更勝一籌。(效能可以參考國外有人做的比較:C++ vs. JavaScript vs. Java vs. Python vs. Perl vs. PHP performance benchmark)。


對於 JavaScript 來說,瀏覽器不再是束縛而是選項

或許有人說,JavaScript 若捨棄了瀏覽器即捨棄掉了最大的優點。但在我的觀點來看,瀏覽器不是被捨棄了,而只是成為了『用 JavaScript 開發使用者介面』的選項之一,若你願意,仍可繼續使用。但為了讓研發使用者介面有更輕量節省資源的選擇,前些日子,我才開發了『node-clutter』(現已改名成為 jsdx-toolkit),做為沒有瀏覽器後,使用 JavaScript 開發 OpenGL/GLES 程式和使用者介面的替代方案。


適用領域

如今的 JavaScript 已和過去不可同日而語,可以用其開發的領域幾乎已經沒有受限。
  1. Web 後端和資料庫系統應用(可取代 Python/Perl/PHP/JSP/ASP/.NET 等技術)
  2. 桌面和手機應用程式(Desktop/Mobile Application)
  3. 手機或平板系統(如取代 JVM 層,並撰寫像 Android Framework 一般的系統框架)
  4. 嵌入式系統(如同手機系統一般,除驅動程式之外的任何研發)
  5. 雲端服務(有 Scalable 的需求)


我相信的新一代 JavaScript 解決方案

這樣新一代的 JavaScript 解決方案,有數不清的優點可以一一詳列,但最重要的有三點:
  1. 使各類嵌入式系統開發更為快速,尤其與使用者介面(User Interface)相關的。
  2. 滿街數不清的 JavaScript 開發者都可以晉升成後端甚至系統工程師, 不再只是末端應用的開發人員。
  3. 統一前後端的開發經驗,讓研發人員在這雲端時代,可以更專注於應用的設計。
同時也打破了過去業界中存在的矛盾與迷思:
  1. JavaScript 和瀏覽器的肥大是共存。
  2. JavaScript 除了用來做 Web 和資料庫系統相關應用之外,不能變成產品化的軟體。
換言之:
  1. 對企業來說:加速開發,省錢,再省錢。
  2. 對傳統 Web 開發者:更有發展空間,更有實際價值。
  3. 對硬底子開發者:讓自己的底層成果能更快被看的見。

那麼,瘋狂服務?

既然  JavaScript 的發展是大勢所趨,未來我們會延續過去的各種經驗,再全面引入 JavaScript,做為自身所有軟體開發的基礎,以 JavaScript 整合並加速產品開發的效率和品質,凡舉雲端服務、Web 、資料庫應用、桌面和手機應用程式、作業系統和嵌入式系統,都是我們將提供的專業服務範圍。

目前,我們已經對 Node.js 和 V8 Engine 有相當程度的瞭解和深入。除了著墨在雲端相關技術的發展,也有不少在作業系統底層和嵌入式裝置,引入 JavaScript 軟體架構的開發經驗。身為 Open Source 開發者,當然也會不定期貢獻成果給上游或釋出相關原始程式碼。

嚴格來說,成為『JavaScript 軟體平台的專家』才是我們的目標,所以我們才說,希望提供『國內最專業的 JavaScript 相關服務』。

2012年3月16日 星期五

Node.js Callback Function 不成文的習慣

Standard
我們常會在一些 Node.js 或是第三方模組的 API 上,看到 callback function 的第一個參數是 err 的情況,雖然官方並沒有明文規定(或許有,只是我沒有看到?),但這樣的習慣已經隨處可見。從官方 API 文件中的範例,就能看到許多例子:
fs.rename('/tmp/hello', '/tmp/world', function (err) {
  if (err) throw err;
  console.log('renamed complete');
});

fs.stat('/tmp/world', function (err, stats) {
  if (err) throw err;
  console.log('stats: ' + JSON.stringify(stats));
});

這樣的情況,最常出現在非同步(asynchronous)執行的函式中。因為這類的函式呼叫,做法是將工作丟到背景等待完成,所以會立即回傳(return),並繼續執行下面的程式。而該函式的工作,在真正完成或有出錯時,會呼叫 callback function。所以,在 callback function 內,我們需要藉由判斷 err 存在與否,去得知該函式是否完成工作或出錯。

至於 err 是什麼格式,對大多開發者來說,他只是個純字串的存在,也就是你可以直接印出它的內容:
console.log(err)

但事實上,err 是 Error 物件,這是 ECMA 有定義的標準物件,對一些有經驗的 JavaScript 開發者來說,應該不陌生,甚至除了 Node.js 之外,在其他的瀏覽器上都有支援。如果想知道他是什麼東西,也可以自己嘗試建立一個 Error 物件來觀察:
var err = new Error('Hello Error!');

比較有趣的是,Error 物件被建立時,會包括 JavaScript Engine Stack 的資訊,這有利於更進一步的 Debug 工作。
console.log(err.stack)

Error: Hello Error!
    at repl:1:6
    at REPLServer.eval (repl.js:80:21)
    at Interface. (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream. (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
    at ReadStream._emitKey (tty.js:327:10)

所以,如果你正在設計一個擁有 callback 的函式,比較好的做法就是遵循這樣有 Error 物件的設計模式。
function waitingforyou(callback) {
    /* Do something */
    data = doSomething();

    if (!data)
        callback(new Error('Something\'s Wrong'));
    else
        callback(null, data);
}

後記

JavaScript 有太多彈性可以讓我們把他搞的亂七八糟,如果不是故意要惡搞,建立一些設計習慣,才是好的選擇。

2012年3月6日 星期二

探討 Node.js 的非同步機制

Standard
Node.js 標榜著事件驅動(Event Drive)的設計,也因為如此,它在網站應用程式的領域上,通常強調有最快速的即時反應。其原理是利用 libev 去實作事件輪詢,不斷的檢查是否有事件需要被處理,一旦發現有事件在待命,就去執行並觸發相應的 Handler。但無可避免的,總會有程式和工作需要佔用大量的 CPU 時間,因此獨佔目前的事件處理程序,造成整個程式被單一事件卡死。這樣的問題,有機會讓原本期望的即時反應機制崩潰。

同樣的問題,也出現在其它 non-blocking 設計的 Framework。Facebook 所開發,也紅過一陣子的 Tornado Web Framework,針對這個問題,就提供了一個 Decorator  -『@tornado.web.asynchronous』,這可以讓 Handler 處於非同步的模式下執行,意味著該段程式可以先丟到背景,而不會讓整個程式為了等待該 Handler 結束,而耗時太久或卡死。

相同的,Node.js 也提供擁有類似的設計,只不過對於語言的使用者來說,概念和用法上不太一樣,因為 Node.js 所提供的 API 目標並不只是單純處理 Web Server 的應用,更像是低階的工作排程器的控制,如作業系統上的 yield() 或 sched_yield()。

我們可以很快的透過一個簡單的例子,去理解怎麼使用他,假設我們開一個檔案讀資料,一般邏輯上的做法(這裡所用的 file 物件是假的,在真實的 Node.js API 中並不存在,只是為了說明方便):
file.open();
while(true) {
    result = file.read();
    if (!result)
        break;

    /* Do something... */
};

console.log('blah blah blah...');
若是這個檔案很大,需要花三分鐘才能讀完,那這個 while 迴圈肯定會鎖死,等讀完後才顯示『blah blah blah...』字樣。想想看,若是這樣的程式被放在 Web Framework 的 Handler 中,肯定會因為這一個使用者,讓其他人三分鐘之內,都無法使用這個網站服務。而實際上,這樣的問題最常出現於檔案上傳的工作上。

若是我們可以用process.nextTick()來改寫,則情況會大不同:
function readLoop() {
    result = file.read();
    if (!result)
        return;

    /* Do something... */
    process.nextTick(readLoop);
}

file.open();
process.nextTick(readLoop);
console.log('blah blah blah...');
結果是『blah blah blah...』字樣會立即出現,檔案讀取工作都會被分段排在每次的排程,這讓事件引擎得以喘口氣,處理其他的工作和事件,整個服務也不會因為單一工作而受阻中斷。

或許你已經發現到了,這樣的做法,其實與過去在瀏覽器製作動畫效果的方法雷同,運用 setTimeout() 去重覆一個會佔用大量時間的工作。為了便於理解,你也可以將它想像成 setTimeout(fn, 0) 的取代,只不過 process.nextTick() 的執行上會更加有效率,這點在 Node.js 的官方文件裡也有特別說明。

如果你有興趣要瞭解 process.nextTick() 是怎麼被實現的,就必需從Node.js 的設計根本面來著手。前面提到,Node.js事件驅動(Event Drive)主要實做於 libev 的基礎之上,Node.js 實作了一個佇列(Queue) 存放事件,而所有的事件觸發,都是由一個輪詢事件的引擎所驅動 。process.nextTick() 就是讓我們可以把程式事件放到佇列(Queue),使其在下一次事件輪詢時被驅動。

你可以參考 Node.js 原始程式碼中的 src/node.js,得到此 API 的實作:
    process._tickCallback = function() {
      var l = nextTickQueue.length;
      if (l === 0) return;

      var q = nextTickQueue;
      nextTickQueue = [];

      try {
        for (var i = 0; i < l; i++) q[i]();
      }
      catch (e) {
        if (i + 1 < l) {<
          nextTickQueue = q.slice(i + 1).concat(nextTickQueue);
        }
        if (nextTickQueue.length) {
          process._needTickCallback();
        }
        throw e; // process.nextTick error, or 'error' event on first tick
      }
    };
    process.nextTick = function(callback) {
      nextTickQueue.push(callback);
      process._needTickCallback();
    };
更多細節事件引擎的細節,讀者可以自行參考 Node.js 的原始程式,就不在本文討論了。

2012年3月4日 星期日

玩什麼雲端?

Standard
雲端這個詞已經吵得鬧哄哄,也有更多人為了這個『形容詞』下了太多的定義,到底什麼是雲端已經變得不重要,也無法三言兩語說的清楚。身為一個資訊產業的創業小老弟,其實並不期待雲端能帶給資訊業如何的革命和新生態,只視為這是一次新契機,讓政府和台灣科技業的老大們,能靜下心傾聽。

我們暫且不需要討論雲端的格局觀,因為這類的畫餅,大從國家政策,小到百姓家常,已經太多了。我們有預算,有政策,有土地,有銀行,有股票,什麼都不缺,只缺少夠多的實際作為而已。其實,回顧過去,可以看到許多事我們現在可以做,但是大多數人,多半用美國矽谷的角度探討當今的資訊產業,我認為,那無法幫助台灣人省視情勢,因為有太多經驗不是在台灣的業界可以仿效或改進的。所以,既然人在台灣,當以台灣出發的角度,省視這些年來的發展過程,因此,筆者將以自己在台灣的經驗,嘗試省視這個產業,雖可能不算準確,卻是一種不同角度的嘗試。

的確,筆者剛滿 26 歲,年紀不足,在商業上,經驗也不算多。但在上一個世紀末,卻曾趕上過十幾年前 .COM 的最後一班列車。至今,看著當年遺留下來的無價紀念品,那些重量級的網域名稱,是花再多錢都無法獲得的一種經驗,令筆者有著無限的感慨和體會。說實在,如果沒這樣做過,永遠不會理解,不受金錢誘惑,堅持自己認為正確的事,然後去惡搞並使用那些當年叫價上億美元的網域名稱,是多麼痛快的事。

很多人會說我是忌妒,眼紅看著在十多年前還是小學生時,曾經發想和完成過的東西,諸如網路相簿、Blog、購物車、集體購物等,還有更多當時翹課躲書店而寫出來的服務,在今天一一出現或成功於網路上。事實上,比起心中的不平衡,我開心自己從中學到更多當時沒有看清楚的事,讓今天的視野可以更開闊。

還記得當時,所有人對網際網路(Internet)都有一種特別的期待,就像今天的雲端(Cloud)魔力一樣。全世界的人也都像現在一樣,砸進了無數的人力物力,在商業界,更是起了許多不得了的名目,打算大賺一筆。然而,絕大部份獲得勝利的人是誰?不是那些有錢有勢的企業和組織,而是真正做事和成本相對低的車庫公司。在某方面來說,這些大企業意外吃了不少悶棍。

從國內的發展來看,雖然在網路發達的今天,網路速度已經不再是大問題,架設網站的成本也不是太高。你可能很難想像當年,要是你有著一條 64K 專線,都可以成為台灣 ISP 公司的情況。也因為如此,當時國內許多想做網站服務的人,都因線路和機房成本,發展受限,或敗陣下來。能夠挺下來的,很多都是後臺夠硬。所以,絕大部份的台灣人都無法想像,後來GMail/Youtube那樣吃大頻寬和大儲存容量的服務,怎麼有人敢想敢做。

不過在這樣的困境下,一些利用學校網路資源的人,一一起家並做大的例子,時有所聞,更常從國外突然掘起的網站服務公司身上看到。從過去的新聞媒體中,也可以找到很多的實例。從這樣的現象不難發現,其實人人都有想法,只是門檻過高而缺少舞台發揮,不只如此,若是在網路環境慢人家幾年的台灣,更是難以有未來。

反觀今天的雲端產業,雖然炒作聲響大,為何大多數有想法的人,依然選擇租一般主機,實作自己的想法,以碰運氣的心態去等待成就,直到真的有些回饋並找到錢,才去打掉重練,開始準備放到大家稱為『雲端』的伺服器上?甚至,根本不考慮『雲端』的解決方案。相同的道理,因為門檻太高,尤其是價格門檻。我們根本不能奢望新創公司或是年輕的學生,可以負擔這樣昂貴的費用,去使用所謂的雲端主機。既然沒有人會去用,政府花再多錢協助企業蓋再多『蚊子機房』也是沒有意義。

同樣的門檻問題,至今還是沒有解決,當年身為沒有經濟能力的小學生是那樣,現在長大後自己創業後也是這樣,我們正在步上過去同樣的路,身為『雲端機房』的需求者,看著『蚊子機房』放在那只能痛心。

至少個人認為,想要在台灣發展雲端重鎮,就要先將過去我們輸掉的時間先追回來。

後記

問題當然不如此單一,只是讓真正需要雲端來發展的人,可以如願使用,比什麼都還來得重要。