`

Browser 與 Server 持續同步的作法介紹 (Polling, Comet, Long Polling, WebSocket)

 
阅读更多

對 Comet 的懵懂

記得兩年多前,第一次看到 Gmail 中的 GTalk 覺得很好奇:「咦?線上聊天且是 Google 的熱門系統,只用傳統的 AJAX 應該會操爆伺服器吧?」很幸運的,當時前公司內部的 Tech Talk 就有位同事分享這個叫 Comet 的技術、是種「為了讓瀏覽器與伺服器頻繁溝通所使用的技術、主要的瓶頸在於 WWW 伺服器上。」但因為工作沒有用到這類的需求、加上找不太到好的入門文章、實作的人不多,因此我對 Comet 的認識一直停留在懵懂的階段。

這一年多,會自動更新的網站越來越多,例如 Twitter、Plurk、Facebook 都會隨時有新資料出現在頁面上。也越來越常聽到 nodeJS 這個框架、似乎成為了此類需求的最佳的解決方案。心中的疑問是:「nodeJS 是專門為了實作 Comet 的 Web 伺服器嗎?」(當然不只是這樣 =b)

一頭霧水的實作階段

最近 miiiCasa 需實作一個即時通知的功能:「當有人做了跟我有關聯的動作時(例如:設為聯絡人、上傳照片到我可以存取的設備),立刻會有一則訊息在左下角。」同事分別將 nodeJS 架設起來並做了分享,似乎萬事皆備只欠 Coding。不過真的開始 Coding、尋找文件時就開始混亂了。因先前錯誤的認知,將許多名詞都混在一起: Polling、AJAX Comet、Comet with Iframe、Non-blocking IO、Web Socket、Long Polling、Socket.io 等。而且還發現 nodeJS 的定位跟我想像的差異很大,本來只知道它是一套事件驅動的伺服器端語言、後來才了解它的強大、甚至可寫出不同類型的伺服器(A HTTP Proxy Server in 20 Lines of node.js Code),它的定位對我來說,根本就是另一套不同概念的 Apache + PHP,即時通知只是其中的一種受歡迎的實作罷了。

先把 nodeJS 放一旁吧(畢竟我對它的了解還在幼稚園階段)。這篇文章主要要介紹的是上面提到的混亂名詞,希望用最簡單的實作讓大家了解每個技術的定義、避免混淆在溝通時造成誤解。

1. 老掉牙的輪詢 - Polling

Polling 的範例

輪詢最常見也最簡單:「利用 JavaScript 的 setInterval(),每隔一段時間就對 Server 發送一個 Request 以 JSONP 或 AJAX 的方式取得最新的資料。」例如:每隔 3 分鐘向伺服器問一次,檢查目前登入 Cookie 是否過期。

JavaScript 的部份

每秒鐘從 Server 取得資料:

YUI().use("node-base", "io", function (Y) {
    Y.on("io:complete", function (id, o, args) {
        Y.one("#show").append(o.responseText);
    }); // 將 Server 成功 Response 的資料寫到頁面上。
    Y.later(1000, null, function () {
        Y.io("polling.php");
    }, null, true); // 每 1 秒用 XMLHttpRequest 向  polling.php 發送 Request。
});
polling

PHP 的部份

範例只是輸出亂數,你可以想像這是從 memcache 或資料庫中取出了幾筆資料:

$num = rand(10, 100); 
echo "Server said $num.\r\n";
exit(); 

優點是非常容易實作、沒有跨瀏覽器的問題、也不需要特殊伺服器做配合。而缺點是沒效率,因多數時間的 Request/Response 的 Header/Content 一致但又不能做 Cache,會因此浪費不必要的頻寬。

2. 舊時代的 Comet - 永不停止的連線

Comet 如同我前面所說,已經有兩三年的歷史了,前端的技術完全無新意可言、後端雖然老舊但會有點 Tricky。Comet 的中文是「彗星」的意思,顧名思義發出的 Request 會像彗星的尾巴一般,拉得很長(一般的 Request 立刻就會結束了)。而這樣做的好處就是可以不結束連線,讓 Server 持續地 Response 資料回 Browser,如此一來就可以解決 Polling 造成頻寬浪費的問題。常見作法有以下兩種:

2-1. 用 AJAX 實作 Comet

AJAX Comet 的範例

首先,必須在 PHP 動些手腳:在 Server 查詢完畢後、利用 flush() 顯示結果再使用 sleep() 暫停執行,依這樣的方式做無窮迴圈。這樣的作法可將 Browser 的 Request 減到最低、但 Server 端仍得用無窮迴圈一直做查詢(可以說是 Server 端的 Polling)。

echo str_repeat(" ", 1024); // 本來我沒辦法產生片段輸出的效果,但先輸出 1024 就可以了,真神奇。
while (TRUE) // 無窮迴圈
{
    $wait = rand(1, 3);
    flush(); // 輸出結果,有人會另外加上 ob_flush()
    $num = rand(10, 100); // 一樣是亂數、可以想成是 ,memcache 或資料庫的查詢。
    echo "Server said $num.\r\n";
    sleep($wait); // 等待一陣子
}

再看看 JavaScript 的部份,其實就是 XMLHttpRequest(也可以是 Script Tag Hack / JSONP)。因為連線不會結束的關係,我們必須使用 readyState = 3 來對回傳的資料做處理。另外由於 PHP 的 flush() 是一直將 response 增加、不是刷新 response,所以我們必須用 substring 才能取得最新的資料。Hmmm... 有點鳥,但這個問題還算可以接受 :p

var node  = Y.one("#show");
try {
    var request = new XMLHttpRequest(); 
} catch (e) {
    alert("Browser doesn't support window.XMLHttpRequest");
}
var pos = 0; // 記下目前的輸出總長度
request.onreadystatechange = function () {
    if (request.readyState === 3) { // 在 Interactive 模式就得處理
        var text = request.responseText; 
        node.append("

" + text.substring(pos) + "

"); // 用前一次的輸出長度擷取最新的字串
        pos = text.length; // 更新總長度
    }
};
request.open("GET", "comet-ajax.php", true); // 傳統的作法,但因 PHP 的特殊處理讓它不會中斷
request.send(null); 
comet-ajax

這個作法有個致命的缺點,就是 IE 沒辦法像 Firefox 或 Chrome 針對 readyState = 3 的資料來做處理。所以... 這個作法的可用性並不高。

2-2 用 Iframe 實作 Comet

範例程式

Iframe 是過去 Comet 中最常見的作法,Server 端的程式幾乎一模一樣,只有輸出的格式改為 HTML、用來輸出一行行的 Inline JavaScript 。由於它一輸出就會執行,就沒有剛剛 XMLHttpRequest 得用 substring 取得最新資料的鳥問題了。重點是每個瀏覽器都可以用,實作起來也相當方便。而此作法的缺點為缺少像是XMLHttpRequest 可利用 readyState 判斷進度、以及 status 判斷連線狀態

PHP 的部份

echo str_repeat(" ", 1024);
while (TRUE)
{
    flush();
    $num = rand(10, 100);
    echo "[script]top.onmessage('Server said $num. ');[/script]";
    sleep(3);
}

JavaScript 的部份

YUI().use("node-base", function (Y) {
    var node  = Y.one("#show"),
        frame = Y.one("iframe");   
    window.callback = function (str) {  // 設定 Iframe 的 Callback 方法
        node.append(str);
    };
    Y.later(10, null, function () { // 只是為了早一點讓 iframe 載入,直接寫 src 太久了
        document.getElementsByTagName("iframe")[0].src = "comet-iframe.php";
    });
});
comet-iframe

Iframe 解決了跨瀏覽器的問題,但所有問題解決了嗎?其實並沒有... 因為 Comet 的這種作法會把將傳統的 Web 伺服器(例如 Apache)的連線給佔住,一個人可能會有多個連線(多個 Tab)、而連線一達到上限卻又沒辦法釋放時,你的網站也就沒辦給更多人使用(IO 被佔滿)。所以 Comet 的技術得配合 Non-Blocking IO 的 Web 伺服器才能運作。另外它也只能由 Server 單方向的供給資料,比起 Polling 每次都可以互動,似乎也是一個麻煩的缺點。像是持續地檢查 Cookie 就沒辦用 Comet 來做、 Polling 才有可能。

3. 改良式 Comet - 長時間的輪詢

範例程式

長時間的輪詢(Long Polling)是 Comet 演化過後的方式、也是目前 Facebook、Plurk 實現動態更新的方法。前面的 Iframe 與 XMLHttpRequest 都屬於「永遠不會斷線」的作法。Long Polling 的作法是發一個長時間等待的 Request、當伺服器有資料 Response 時立刻斷掉、接著再發一個新的 Request

JavaScript 的部份

其實若 Server 沒有支援,它就是一個 Polling 的程式碼(一個結束後再做一個):

YUI().use("jsonp", "node-base", function (Y) {
    var handler = function (response) {
        Y.one("#show").append("[p]" + response.result + "[/p]");
        Y.jsonp("http://comet.josephj.com/?callback={callback}", arguments.callee);
    };
    Y.jsonp("http://comet.josephj.com/?callback={callback}", handler);
});

nodeJS 的部份

因為 Long Polling 是目前的主流,我也用主流的 nodeJS 來寫吧。下面的 setTimeout 只是為了等待的效果,其實在實作時是可以不用對 Server 做 Polling 的,採用其他方式驅動事件才是 nodeJS 的精神。

var http = require("http"),
    url  = require("url"),
    qs   = require("querystring");
httpServer = http.createServer(function (request, response) {
    var callback = qs.parse(url.parse(request.url).query).callback; // 取得 callback GET 參數
    setTimeout(function () { //  3 秒後(只是為了達成等待的效果)就輸出 JSONP 格式,可以想成每一段時間就去 DB 或 memcache 查詢。
        var text = callback + "({'result': 'Server said " + parseInt(new Date().getTime(), 10) + "'});";
        response.write(text);
        response.end(); // 結束連線。
    }, 3000);
    response.writeHead(200, {"Content-Type": "text/javascript"});
}).listen(1387);
long-polling

與 Polling 的不同之處就在於它是比較有效率的、可以等到 timeout 或拿到資料時再重新發、因此減少不必要的流量浪費。另外,跟舊型態的 Comet 比起來,Browser 比較有機會傳遞資料(每次發新的 Request 的時候)。加上沒有瀏覽器相容性的問題,難怪它會成為當今最常見的解法了。

4. 明日之星 - WebSocket

上面所講的幾種方法,除了 Polling 外,全部都有僅單向溝通的問題。HTML5 的 WebSocket 解決了此問題。他利用新的協定建立了雙向的通道:當通道建立起來之後,Browser 可以隨時丟訊息給 Server、Server 可以隨時丟訊息給 Browser。非常地方便好用。唯一的缺點就是當今瀏覽器的支援度不普及(IE9 不支援、Chrome 支援、FF4 未知)

JavaScript 的部份

範例程式(因為 Server 有 Proxy,所以沒辦法順利成功,但在直接連線的環境並且使用 Chrome 是沒問題的)
YUI().use("node-base", function (Y) {
	var node = Y.one("#show");
	var conn = new WebSocket("ws://node.josephj.com/test");
	conn.onopen = function (e) { // 當通道建立完畢時
		Y.later(3000, null, function () { // 每三秒往 Server 塞資料
			conn.send("Browser said " + parseInt(new Date().getTime()) + ".");
		}, null, true);
	};
	conn.onmessage = function (e) { // 當收到 Server 的資料時
		node.append("[p]" + e.data + "[/p]"); // 顯示在頁面上
	};
});

nodeJS 的部份

範例程式

因為 WebSocket 是另外一個協定,我套用了現成的 node-websocket-server 來達成。

var ws   = require(__dirname + "/node-websocket-server/lib/ws/server"),
    server;
server = ws.createServer(); // 建立 WebSocket 伺服器
server.addListener("connection", function (conn) { // 當與 Client 連線順利建立
    conn.addListener("message", function (message) { // 當收到 Client 的連線
        var text = "<" + conn.id + "> " + message + ".";
        conn.send(text); // 將資料送回 Client(製造雙通道的效果)
    });
    setInterval(function () {
        conn.send("Server said " + parseInt(new Date().getTime(), 10) + "."); // 持續的將資料送回 Client
    }, 5000);
});
server.listen(1388);
WebSocket

寫起程式真的直覺多了,不是嗎?另外聽同事說 Socket.io 是完整解決方案,包含前後端函式庫,另外當 Browser 不支援時還有 fallback (應該是恢復使用 Long Polling)。有機會來玩 :D(註:WebSocket disabled in Firefox 4:2010/12 目前 Opera 跟 Firefox 都宣告 WebSocket 是個不安全的 Protocol、暫時無法讓開發者使用、必須修正之後再開放。我在 Chrome 9 是可以順利執行的)

結語

全部想清楚、並且都實作出來,花了我一整天的時間(特別是 WebSocket,因有架 Proxy 導致一直失敗、建議大家在試上面的所有範例時都不要有 Proxy)。唯一的好處就是搞懂 Comet 這個名詞至少代表了三種實作方法、Long Polling 則是其中的一種、也是目前最熱門的。實作的方向仍然不變囉。希望對有興趣使用的朋友有幫助。所有範例都放在 GitHub

推薦連結

 

转载:http://josephj.com/entry.php?id=358

分享到:
评论

相关推荐

    tomcat+comet实现终端与服务端同步的小例子

    【标题】:“Tomcat+Comet 实现终端与服务端同步的小例子” 在这个主题中,我们将探讨如何使用Apache Tomcat服务器和Comet技术来实现浏览器客户端与服务端的实时通信。Comet是一种Web应用程序设计模式,它允许数据...

    webSocket相关技术

    这里主要介绍三种Comet技术:轮询(polling)、流(streaming)和长轮询(long polling)。 1. 轮询(Polling): 轮询是最基本的Comet实现方式,客户端定时向服务器发送请求,询问是否有新数据。服务器在接收到...

    WinHTTP WebSocket 代码

    This is made possible by providing a standardized way for the server to send content to the browser without being solicited by the client, and allowing for messages to be passed back and forth while ...

    WebSocket 超详细中文教程

    - **长轮询(Ajax Long Polling)**:客户端向服务器发起请求后,若服务器暂时没有新数据,则不会立即返回响应,而是保持连接直到有新数据到达再返回。相比短轮询,这种方式减少了不必要的请求,但仍然不是最优解。 - ...

    java-comet

    2. 长轮询(Long Polling):Java-Comet通常基于长轮询机制工作。客户端向服务器发送请求,服务器接收到请求后不立即返回,而是保持连接直到有新数据可用或超时,然后才将数据发送回客户端并关闭连接。这样客户端...

    comet4j完整资源

    1. **长轮询(Long Polling)**:Comet4j可能实现了长轮询技术,这是最早的Comet实现方式之一。当客户端发送请求后,服务器并不立即响应,而是保持连接打开状态,直到有新的数据可发送,或者超时才返回,从而实现数据...

    comet4j-js

    Comet4j-js 是一种基于Java的实时通信技术,它允许服务器主动向浏览器推送信息,而无需客户端持续发送请求。这种技术在Web开发中非常有用,可以实现如聊天室、实时股票更新、在线游戏等实时交互功能。在Tomcat6和...

    java websocket

    在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。 轮询是最原始的实现实时Web应用的解决...

    Comet4j完整demo

    Comet4j是一个开源的Java库,专门设计用于处理长连接(Long-Polling)通信,从而允许服务器主动向客户端推送数据,而不仅仅是响应客户端请求。这种技术在实时应用,如聊天、股票更新、在线游戏等场景中非常有用。 ...

    comet4j-tomcat6.jar和comet4j-tomcat7.jar和comet4j.js

    关于Comet4j的工作原理,它通常利用了HTTP的特性,如长时间连接(Long Polling)或HTTP流(HTTP Streaming),保持一个打开的HTTP连接直到服务器有数据要发送。这种方式减少了HTTP请求和响应的开销,提高了数据传输...

    Flex与java Server Push数据

    7. **安全性与优化**:由于Server Push涉及持续的网络连接,需要考虑连接管理、错误处理、安全性和性能优化等问题。 8. **示例代码**:`pushDemo`可能是一个包含示例代码的压缩包,用于演示如何在Flex中设置...

    pushlet 和comet 资料介绍

    Comet技术则是一个更广泛的概念,它包括多种实现方式,如Long-Polling、Streaming和HTTP 200 Keep-Alive等。Pushlet是Comet技术的一种具体实现,它们都旨在解决Web应用中的实时性问题。在服务器端,Pushlet使用`...

    my_comet_demo.rar_DEMO_comet

    Comet主要有两种实现方式:长轮询(Long Polling)和HTTP流(HTTP Streaming)。长轮询是客户端发起一个请求,服务器在没有新数据时保持连接不关闭,直到有数据可用时才返回响应,客户端再发起新的请求。HTTP流则是...

    PHP防Sina微薄无刷新服务器推送(comet)原理

    1. **长轮询(Long Polling)**:客户端向服务器发送请求,服务器接收请求后并不立即响应,而是等待有新数据可用时才返回。如果在预设的超时时间内没有新数据,服务器会返回一个空响应,客户端收到响应后再次发起请求...

    Java 实现 Comet 长连接,服务器主动发送消息给客户端

    Comet 技术主要分为两种实现方式:HTTP 长轮询(Long Polling)和 HTTP 流(HTTP Streaming)。长轮询是客户端发起请求,服务器在没有新数据时保持连接不关闭,直到有新数据或达到预设超时时间才返回响应并关闭连接...

    服务端推技术 - Server-side Push 多示例

    常见的Comet实现方式包括长轮询(Long Polling)和流(Streaming)。长轮询是客户端发起一个请求,服务器保持请求不响应直到有新数据,然后一次性返回;流则是服务器持续发送数据到客户端,直到连接中断。 2. **...

    comet技术研究总结

    Comet技术是一种在Web开发中用于实现实时通信的技术,它的核心理念是通过持久化连接(Long Polling、Streaming、HTTP/2 Server Push等)来打破传统的HTTP请求-响应模式,使得服务器能够主动向客户端推送数据,而无需...

    WebSocket简单使用.pdf

    3. **流(Streaming)/Comet**:服务器维持一个长连接,持续向客户端推送数据。然而,这可能导致浏览器兼容性问题,并增加服务器资源压力。 WebSocket协议的出现提供了一种更有效的方法。它通过JavaScript调用...

    基于comet4j的简易聊天

    相比WebSocket等其他技术,Comet4j在兼容性上更胜一筹,可以很好地运行在各种浏览器和服务器环境中。 3. **聊天应用架构**:在这个项目中,可能包括服务器端和客户端两部分。服务器端负责处理用户的登录、注册、...

    WebSocket学习文档

    2. **长轮询(Long Polling)**:服务器在接收到请求后保持连接,直到有新数据才响应并关闭连接,客户端再重新发起请求。长轮询降低了请求频率,但仍然存在额外的开销。 3. **Comet技术**:包括服务器推(Server-Sent ...

Global site tag (gtag.js) - Google Analytics