2011年11月5日 星期六

使用 NodeJS + Express + Socket.IO 實作 WebSocket 服務

Standard
一直處於假標準狀態的 HTML5,過程中一直有變化,讓人無法放心使用。不過到了今年,HTML5 總算進入了準決賽的階段,大部份功能已經確定下來,其中 WebSocket 就是最讓人關切的項目之一,因為這意味著未來 Web 不再只是單次性的觸發服務,而是可以讓使用者端與伺服器長時間處於連線狀態,並進行即時性的資料傳交換。

關於 WebSocket,彷間許多隨 HTML5 起舞的人,其實都說明不夠清楚,多半只強調能達成 TCP Socket 形式的連線,和無所不能的優點。其實,WebSocket 並非是我們所知的原生 Socket ,而只是一種架構於 HTTP Protocol 上的延伸定義,所以換句話來說,不能直接拿來連線到非 HTTP 以外的通訊協定。

至於它的做法,就是在瀏覽器向伺服器要資料時,在 Request Header 裡加上『Connection:Upgrade』來告訴伺服器:『我要改變連線形態』。然後伺服器會依照瀏覽器給的 WebSocket 相關標頭(header)和交握用的金鑰(Key),進行認證和改變處理方式。進入 WebSocket 連線的客戶端,就有如在使用 TCP Socket 或 Telnet 一樣,可以隨意在任意時間點傳送資料給伺服器處理,伺服器也可以在這連線下即時回應。所以就某方面來說,這剛好可以含蓋並取代過去 Web 1.0 的 Refresh Page 和 Web 2.0 後的 Long-polling 技術。

寫過網路程式的人都知道,如何定義 Protocol 才是傷腦筋的事,開啟了一個『類 Socket』意味著我們又回到了過去的年代,要自己解決 Protocol 的定義。不過也已經有現成的 Library 可以使用,許多問題我們已經不用擔心,本文將提到的 Socket.IO 就是其中之一。此外,由於 WebSocket 需要 Client/Server 同時實作,所以,這些 Library 多半都包括了兩部份的實作。

Server 端,使用 NodeJS + Express web framework + Socket.IO(0.8.6):
/* Module dependencies */
var express = require('express');
var io = require('socket.io');

/* Initializing Express Framework */
var app = module.exports = express.createServer();
app.listen(3000);

/* Create a Socket.IO instance, to establish WebSocket Service */
var socket = io.listen(app);
socket
    .on('connection', function(client) {
        console.log('A connection was established');
        client
            .on('pretty-girl', function(data) {
                console.log(data);
                socket.emit('ugly-man', 'get out of my way');
            });

        client.on('disconnect', function() {
            console.log('Server has disconnected');
        });
    });

Client 端,使用 Socket.IO(0.8.6):
socket = io.connect();
socket.on('connect', function() {
    console.log('Client has connected to the server!');
})
socket.on('disconnect', function() {
    console.log('The client has disconnected!');
});
socket.on('ugly-man', function(data) {
    console.log(data);
});
socket.emit('pretty-girl', 'hello!');

後記

回到原始的網路溝通,也有許多議題將再次面臨,舉例來說,客戶端(Client)可以同時發送很多道命令給伺服器,但伺服器可能每道命令完成時間有快有慢,以至有些命令是後發卻先完成。那伺服器如何告知客戶端,是哪一道命令完成了?而這種問題非常容易在多功能的服務中見到,尤其是遊戲伺服器。不過,對於過去有過伺服器設計經驗的人,顯然是不用怕。

備註:使用 Socket.IO 要注意版本的變化,因為各版 API 改動伏度都不小。而且也要注意 Client/Server 要使用同一版本,之前筆者犯錯,在 Client 使用官方提供的 CDN 版本,但在 Server 端使用自己下載的最新版本,結果一直無法讓 WebSocket 成功被建立。