node.js chat程序如何實現(xiàn)Ajax long-polling長鏈接刷新模式
更新時間:2012年03月13日 22:59:48 作者:
node.js chat是node.js作者用JS寫的一個多人聊天工具, 源代碼公開下載,網(wǎng)址是chat.nodejs.org。作者用這個小例子,來展示如何用nodejs開發(fā)高效率的應(yīng)用程序。對于nodejs的學(xué)習(xí)者來說,是一個很好的例子
廢話不多說,開始今天的主題??v觀這個程序,感覺它的最可貴之處,在于展示了,如何用nodejs實現(xiàn)長鏈接模式的刷新技術(shù)。
?。ㄟ@個程序不詳細(xì)介紹,重點講解這個功能)
Client.js
首先看一段核心代碼:
function longPoll (data) {
//....此處省略**行
$.ajax({ cache: false
, type: "GET"
, url: "/recv"
, dataType: "json"
, data: { since: CONFIG.last_message_time, id: CONFIG.id }
, error: function () {
addMessage("", "long poll error. trying again...", new Date(), "error");
transmission_errors += 1;
//don't flood the servers on error, wait 10 seconds before retrying
setTimeout(longPoll, 10*1000);
}
, success: function (data) {
transmission_errors = 0;
//if everything went well, begin another request immediately
//the server will take a long time to respond
//how long? well, it will wait until there is another message
//and then it will return it to us and close the connection.
//since the connection is closed when we get data, we longPoll again
longPoll(data);
}
});
}
這是client.js中的一段代碼,一看這段代碼,大家應(yīng)該立馬想到兩個字——“遞歸”。在longPoll方法中,再次調(diào)用longPoll方法,典型的遞歸調(diào)用。
根據(jù)這段代碼的語義,可以看出,第一次加載時,會調(diào)用longPoll方法,異步向"/resv"獲取值,如果成功了, 執(zhí)行success的方法,立即再次調(diào)用longPoll方法。如果失敗了,執(zhí)行error函數(shù),隔10秒中再次調(diào)用longPoll方法。當(dāng)然,執(zhí)行error方法有一定的次數(shù)限制,由變量transmission_errorsx控制。
大家可能會有一個疑問,這樣一直遞歸循環(huán)獲取數(shù)據(jù),服務(wù)器會不會有很大的負(fù)擔(dān)?在沒有數(shù)據(jù)可獲取的時候,也會一直這樣循環(huán)嗎?當(dāng)然,答案時否定的!并且,nodejs利用自身的特點,很好的處理了這個問題。接著往下看:
Server.js
現(xiàn)看server中如何回應(yīng)上面client的調(diào)用,核心代碼:
fu.get("/recv", function (req, res) {
//對session的驗證和更新......
channel.query(since, function (messages) {
if (session) session.poke();
res.simpleJSON(200, { messages: messages, rss: mem.rss });
});
});
先不要管這個fu.get()是什么意思,它和本次教程無關(guān)。總之知道它能回應(yīng)client的調(diào)用就行了。上面的代碼,除了對session的一些操作之外,只是調(diào)用了channel的query方法。注意傳遞的參數(shù):
since,它紀(jì)錄了一個時間;
匿名方法,它接受一個messages參數(shù),兩個動作:1 更新session時間,2 返回一個json,即把messages返回給客戶端。
有人可能會有疑問:在這里直接返回messages不行嗎,干嘛還得在一個channel中定義一個方法才操作?答案:如果是那樣,就成了一個死循環(huán),server和client每時每刻都進(jìn)行著數(shù)據(jù)交互,即使沒有信息可返回。
還是接著往下看吧!
看channel是怎么定義的:
var MESSAGE_BACKLOG = 200,
SESSION_TIMEOUT = 60 * 1000;
var channel = new function () {
var messages = [],
callbacks = [];
this.appendMessage = function (nick, type, text) {
var m = { nick: nick
, type: type // "msg", "join", "part"
, text: text
, timestamp: (new Date()).getTime()
};
switch (type) {
case "msg":
sys.puts("<" + nick + "> " + text);
break;
case "join":
sys.puts(nick + " join");
break;
case "part":
sys.puts(nick + " part");
break;
}
messages.push( m );
while (callbacks.length > 0) {
//shift() 方法用于把數(shù)組的第一個元素從其中刪除,并返回第一個元素的值
callbacks.shift().callback([m]);
}
while (messages.length > MESSAGE_BACKLOG)
messages.shift();
};
this.query = function (since, callback) {
var matching = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
if (message.timestamp > since)
matching.push(message)
}
if (matching.length != 0) {
callback(matching);
} else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
};
// clear old callbacks
// they can hang around for at most 30 seconds.
setInterval(function () {
var now = new Date();
while (callbacks.length > 0 && now - callbacks[0].timestamp > 30*1000) {
callbacks.shift().callback([]);
}
}, 3000);
};
channel中定義了兩個變量,兩個方法,還有一個每隔3秒執(zhí)行一次的setInterval函數(shù)。
首先看query方法,
query方法接收兩個參數(shù):
since:紀(jì)錄一個時間
callback:即上面講調(diào)用channel.query方法時傳入的那個匿名函數(shù)(JS中,函數(shù)可以當(dāng)參數(shù)傳遞,接收之后可直接調(diào)用。不會趕快補(bǔ)課啊。。。)
messages里存的時當(dāng)前的聊天紀(jì)錄隊列,query方法會查找符合條件的聊天紀(jì)錄,把他們放在matching隊列中。如果matching.length>0,則調(diào)用callback接收的函數(shù),即把matching以json格式返回client。但是。。。接下來是重點?。?!
if (matching.length != 0) {
callback(matching);
} else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
如果matching.length<=0,程序會把callback和當(dāng)前時間,以json格式,存入一個callbacks隊列中。對!不執(zhí)行了,因為沒有符合條件的聊天消息,不需要在client顯示,所以先把這個函數(shù)存起來,先不執(zhí)行。
那也不能這樣一直存著啊,萬一下一秒有人發(fā)聊天消息怎么辦?
接著看appendMessage(添加聊天消息)方法:
該方法中,前段部分很好理解,無非是接收傳入的參數(shù),組合成一個m集合,然后用sys.puts在終端顯示一下,再把m插入到messages聊天消息隊列中。接下來又是重點:
while (callbacks.length > 0) {
//shift() 方法用于把數(shù)組的第一個元素從其中刪除,并返回第一個元素的值
callbacks.shift().callback([m]);
}
現(xiàn)在要判斷callbacks有沒有儲存,如果有,就執(zhí)行一個,刪除一個,知道執(zhí)行完了為止。因為之前在沒有聊天消息可返回的時候,有人發(fā)出了請求,然后系統(tǒng)沒有執(zhí)行這些請求,都把他們放在callbacks列表中了。
現(xiàn)在有人發(fā)送了聊天消息,執(zhí)行添加方法的時候,要再次把那些沒執(zhí)行的請求都執(zhí)行一遍。
通俗理解可以是:你給我發(fā)送請求了,我現(xiàn)在還沒有新消息可以答復(fù)你,一旦有人發(fā)了新消息,我會立刻答復(fù)你。
不知道大家理解了沒。。。
這一步完了,最后一步,是每隔3秒鐘,清空過期的callbacks,是一個setInterval函數(shù),這個不難理解。
總結(jié)
nodejs用自身基于事件驅(qū)動的語言特點,實現(xiàn)了長鏈接刷新功能,讓我們打開眼界。自我感覺受益匪淺。特此花時間寫個教程跟大家分享,同時也加深我自己的理解。
?。ㄟ@個程序不詳細(xì)介紹,重點講解這個功能)
Client.js
首先看一段核心代碼:
復(fù)制代碼 代碼如下:
function longPoll (data) {
//....此處省略**行
$.ajax({ cache: false
, type: "GET"
, url: "/recv"
, dataType: "json"
, data: { since: CONFIG.last_message_time, id: CONFIG.id }
, error: function () {
addMessage("", "long poll error. trying again...", new Date(), "error");
transmission_errors += 1;
//don't flood the servers on error, wait 10 seconds before retrying
setTimeout(longPoll, 10*1000);
}
, success: function (data) {
transmission_errors = 0;
//if everything went well, begin another request immediately
//the server will take a long time to respond
//how long? well, it will wait until there is another message
//and then it will return it to us and close the connection.
//since the connection is closed when we get data, we longPoll again
longPoll(data);
}
});
}
這是client.js中的一段代碼,一看這段代碼,大家應(yīng)該立馬想到兩個字——“遞歸”。在longPoll方法中,再次調(diào)用longPoll方法,典型的遞歸調(diào)用。
根據(jù)這段代碼的語義,可以看出,第一次加載時,會調(diào)用longPoll方法,異步向"/resv"獲取值,如果成功了, 執(zhí)行success的方法,立即再次調(diào)用longPoll方法。如果失敗了,執(zhí)行error函數(shù),隔10秒中再次調(diào)用longPoll方法。當(dāng)然,執(zhí)行error方法有一定的次數(shù)限制,由變量transmission_errorsx控制。
大家可能會有一個疑問,這樣一直遞歸循環(huán)獲取數(shù)據(jù),服務(wù)器會不會有很大的負(fù)擔(dān)?在沒有數(shù)據(jù)可獲取的時候,也會一直這樣循環(huán)嗎?當(dāng)然,答案時否定的!并且,nodejs利用自身的特點,很好的處理了這個問題。接著往下看:
Server.js
現(xiàn)看server中如何回應(yīng)上面client的調(diào)用,核心代碼:
復(fù)制代碼 代碼如下:
fu.get("/recv", function (req, res) {
//對session的驗證和更新......
channel.query(since, function (messages) {
if (session) session.poke();
res.simpleJSON(200, { messages: messages, rss: mem.rss });
});
});
先不要管這個fu.get()是什么意思,它和本次教程無關(guān)。總之知道它能回應(yīng)client的調(diào)用就行了。上面的代碼,除了對session的一些操作之外,只是調(diào)用了channel的query方法。注意傳遞的參數(shù):
since,它紀(jì)錄了一個時間;
匿名方法,它接受一個messages參數(shù),兩個動作:1 更新session時間,2 返回一個json,即把messages返回給客戶端。
有人可能會有疑問:在這里直接返回messages不行嗎,干嘛還得在一個channel中定義一個方法才操作?答案:如果是那樣,就成了一個死循環(huán),server和client每時每刻都進(jìn)行著數(shù)據(jù)交互,即使沒有信息可返回。
還是接著往下看吧!
看channel是怎么定義的:
復(fù)制代碼 代碼如下:
var MESSAGE_BACKLOG = 200,
SESSION_TIMEOUT = 60 * 1000;
var channel = new function () {
var messages = [],
callbacks = [];
this.appendMessage = function (nick, type, text) {
var m = { nick: nick
, type: type // "msg", "join", "part"
, text: text
, timestamp: (new Date()).getTime()
};
switch (type) {
case "msg":
sys.puts("<" + nick + "> " + text);
break;
case "join":
sys.puts(nick + " join");
break;
case "part":
sys.puts(nick + " part");
break;
}
messages.push( m );
while (callbacks.length > 0) {
//shift() 方法用于把數(shù)組的第一個元素從其中刪除,并返回第一個元素的值
callbacks.shift().callback([m]);
}
while (messages.length > MESSAGE_BACKLOG)
messages.shift();
};
this.query = function (since, callback) {
var matching = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
if (message.timestamp > since)
matching.push(message)
}
if (matching.length != 0) {
callback(matching);
} else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
};
// clear old callbacks
// they can hang around for at most 30 seconds.
setInterval(function () {
var now = new Date();
while (callbacks.length > 0 && now - callbacks[0].timestamp > 30*1000) {
callbacks.shift().callback([]);
}
}, 3000);
};
channel中定義了兩個變量,兩個方法,還有一個每隔3秒執(zhí)行一次的setInterval函數(shù)。
首先看query方法,
query方法接收兩個參數(shù):
since:紀(jì)錄一個時間
callback:即上面講調(diào)用channel.query方法時傳入的那個匿名函數(shù)(JS中,函數(shù)可以當(dāng)參數(shù)傳遞,接收之后可直接調(diào)用。不會趕快補(bǔ)課啊。。。)
messages里存的時當(dāng)前的聊天紀(jì)錄隊列,query方法會查找符合條件的聊天紀(jì)錄,把他們放在matching隊列中。如果matching.length>0,則調(diào)用callback接收的函數(shù),即把matching以json格式返回client。但是。。。接下來是重點?。?!
復(fù)制代碼 代碼如下:
if (matching.length != 0) {
callback(matching);
} else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
如果matching.length<=0,程序會把callback和當(dāng)前時間,以json格式,存入一個callbacks隊列中。對!不執(zhí)行了,因為沒有符合條件的聊天消息,不需要在client顯示,所以先把這個函數(shù)存起來,先不執(zhí)行。
那也不能這樣一直存著啊,萬一下一秒有人發(fā)聊天消息怎么辦?
接著看appendMessage(添加聊天消息)方法:
該方法中,前段部分很好理解,無非是接收傳入的參數(shù),組合成一個m集合,然后用sys.puts在終端顯示一下,再把m插入到messages聊天消息隊列中。接下來又是重點:
復(fù)制代碼 代碼如下:
while (callbacks.length > 0) {
//shift() 方法用于把數(shù)組的第一個元素從其中刪除,并返回第一個元素的值
callbacks.shift().callback([m]);
}
現(xiàn)在要判斷callbacks有沒有儲存,如果有,就執(zhí)行一個,刪除一個,知道執(zhí)行完了為止。因為之前在沒有聊天消息可返回的時候,有人發(fā)出了請求,然后系統(tǒng)沒有執(zhí)行這些請求,都把他們放在callbacks列表中了。
現(xiàn)在有人發(fā)送了聊天消息,執(zhí)行添加方法的時候,要再次把那些沒執(zhí)行的請求都執(zhí)行一遍。
通俗理解可以是:你給我發(fā)送請求了,我現(xiàn)在還沒有新消息可以答復(fù)你,一旦有人發(fā)了新消息,我會立刻答復(fù)你。
不知道大家理解了沒。。。
這一步完了,最后一步,是每隔3秒鐘,清空過期的callbacks,是一個setInterval函數(shù),這個不難理解。
總結(jié)
nodejs用自身基于事件驅(qū)動的語言特點,實現(xiàn)了長鏈接刷新功能,讓我們打開眼界。自我感覺受益匪淺。特此花時間寫個教程跟大家分享,同時也加深我自己的理解。
您可能感興趣的文章:
- node.js+jQuery實現(xiàn)用戶登錄注冊AJAX交互
- Node.js如何響應(yīng)Ajax的POST請求并且保存為JSON文件詳解
- Node.js獲取前端ajax提交的request信息
- Node.js配合node-http-proxy解決本地開發(fā)ajax跨域問題
- Node.js服務(wù)器環(huán)境下使用Mock.js攔截AJAX請求的教程
- node.js+Ajax實現(xiàn)獲取HTTP服務(wù)器返回數(shù)據(jù)
- Ajax異步文件上傳與NodeJS express服務(wù)端處理
- Ajax 的初步實現(xiàn)(使用vscode+node.js+express框架)
相關(guān)文章
通過函數(shù)作用域和塊級作用域看javascript的作用域鏈
這篇文章給大家分享了通過函數(shù)作用域和塊級作用域看javascript的作用域鏈的相關(guān)知識點內(nèi)容,有興趣的朋友參考學(xué)習(xí)下。2018-08-08layui使用button按鈕 點擊出現(xiàn)彈層 彈層中加載表單的實例
今天小編就為大家分享一篇layui使用button按鈕 點擊出現(xiàn)彈層 彈層中加載表單的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09JS中touchstart事件與click事件沖突的解決方法
這篇文章主要給大家介紹了關(guān)于JS中touchstart事件與click事件沖突的解決方法,文中通過示例代碼將解決的方法介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03JavaScript遍歷table表格中的某行某列并打印其值
這篇文章主要介紹了JavaScript遍歷table表格中的某行某列并打印其值,需要的朋友可以參考下2014-07-07JS實現(xiàn)表格數(shù)據(jù)各種搜索功能的方法
這篇文章主要介紹了JS實現(xiàn)表格數(shù)據(jù)各種搜索功能的方法,可實現(xiàn)忽略大小寫,模糊搜索,多關(guān)鍵搜索等功能,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03JavaScript中實現(xiàn)new的兩種方式引發(fā)的探究
眾所周知JS中new的作用是通過構(gòu)造函數(shù)來創(chuàng)建一個實例對象,這篇文章主要給大家介紹了關(guān)于JavaScript中實現(xiàn)new的兩種方式引發(fā)的相關(guān)資料,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05調(diào)試Javascript代碼(瀏覽器F12及VS中debugger關(guān)鍵字)
目前,常用的瀏覽器IE、Chrome、Firefox都有相應(yīng)的腳本調(diào)試功能下面我就介紹如何在瀏覽器/VS中調(diào)試我們的JS代碼,感興趣的你可不要走開啊,希望本文對你有所幫助2013-01-01