`
j2002sx
  • 浏览: 34436 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

與 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

推薦連結

分享到:
评论

相关推荐

    WinHTTP WebSocket 代码

    and the WebSocket API for in Web IDL is being standardized by the W3C.WebSocket is designed to be implemented in web browsers and web servers, but it can be used by any client or server application....

    GolangHTTP的longpolling库Golongpoll.zip

    Golongpoll 是 golang HTTP 的 longpolling 库,可以使构建 web pub-sub 更加容易。基本用法:import "github.com/jcuga/golongpoll" // This launches a goroutine and creates channels for all the ...

    webSocket相关技术

    webSocket相关的技术的源代码,包括轮询(polling)、流(streaming)和长轮询(long polling) 实现的comet

    websocket.zip

    websocket所需js文件:stomp.js、sockjs.js Stomp websocket使用socket实现双工异步通信能力。但是如果直接使用websocket协议开发程序比较繁琐,我们可以使用它的子协议Stomp SockJS sockjs是websocket协议的...

    java websocket

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

    The WebSocket Protocol

    The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host ...XMLHttpRequest or &lt;iframe&gt;s and long polling).

    RFC 6455 - The WebSocket Protocol

    The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from...s and long polling).

    longpolling_room:长投票聊天室样本

    longpolling_room 这是一个实作Long polling 机制的web 程式没有多余不相干的功能,纯粹的comet programming 基本范例 Long polling的介绍可参考这篇 程式介绍 index.php 为主程式进入点 allmsg.php 为第一次程式...

    html5 websocket

    WebSocket reduces latency because once the WebSocket connection is established, the server can send messages as they become available. For example, unlike polling, WebSocket makes a single request. ...

    Python库 | polling2-0.4.7.tar.gz

    资源分类:Python库 所属语言:Python 资源全名:polling2-0.4.7.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    long_polling_select.zip--Oracle数据库LONG类型字段的完整C代码(POLLING SELECT)

    Oracle数据库LONG类型字段的完整C代码操作(CREATE INSERT SELECT DROP)之六--分段轮询SELECT;免费下载有猫彬为你准备的代码包(已编译好,可直接运行,内含编译运行命令,直接复制粘贴即可

    long_polling_insert.zip--Oracle数据库LONG类型字段的完整C代码(POLLING INSERT)

    Oracle数据库LONG类型字段的完整C代码操作(CREATE INSERT SELECT DROP)之五--分段轮询INSERT;免费下载有猫彬为你准备的代码包(已编译好,可直接运行,内含编译运行命令,直接复制粘贴即可

    Web socket长连接技术介绍v1.pptx

    LongPolling是在Polling上的一些改进,在一定程度解决了带宽资源和CPU内存浪费情况,但是当服务端数据更新很快,这和Polling没有本质上的区别,而且http数据包的头部数据量往往很大,但是真正被服务器需要的数据却很...

    Ajax-ajax-long-polling-chat.zip

    Ajax-ajax-long-polling-chat.zip,简单的php 长轮询聊天webapp。,ajax代表异步javascript和xml。它是多种web技术的集合,包括html、css、json、xml和javascript。它用于创建动态网页,其中网页的小部分在不重新加载...

    .NET Core 基于Websocket的在线聊天室实现

    什么是Websocket 我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后就会断开连接。 在没有...

    Polling_dma_

    lpc17xx_libcfg.h: Library configuration file - include needed driver library for this example makefile: Example's makefile (to build with GNU toolchain)adc_burst_test.c: Main program

    GD32 MCU 开发板 Workshop 报告:GD32E231C-Start开发板按键长短按识别-GPIO_Key_Polling_mode.zip

    GD32 MCU 开发板 Workshop 报告:GD32E231C-Start开发板按键长短按识别-GPIO_Key_Polling_mode.zip

    SDIO-Polling mode.rar

    基于STM32F103ZETX芯片,使用STM32CubeMx实现SDIO轮询方式读写数据的源文件。

    2-M3-USART1(polling)_uart_

    两个12位带缓冲的DAC通道可以用于转换2路数字信号成为2路模拟电压信号并输出。这项功能内部是通过集成的电阻串和反向的放大器实现。这个双数字接口支持下述功能:参照2009年3月 STM32F103xCDE数据手册 英文第5版

    Peter's Polling Package

    Peter's Polling Package Highly customizable Polling/Voting controls for asp.net web sites

Global site tag (gtag.js) - Google Analytics