node.js chat程序如何实现Ajax long-polling长链接刷新模式


Posted in Javascript onMarch 13, 2012

废话不多说,开始今天的主题。纵观这个程序,感觉它的最可贵之处,在于展示了,如何用nodejs实现长链接模式的刷新技术。

(这个程序不详细介绍,重点讲解这个功能)
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中的一段代码,一看这段代码,大家应该立马想到两个字——“递归”。在longPoll方法中,再次调用longPoll方法,典型的递归调用。

根据这段代码的语义,可以看出,第一次加载时,会调用longPoll方法,异步向"/resv"获取值,如果成功了, 执行success的方法,立即再次调用longPoll方法。如果失败了,执行error函数,隔10秒中再次调用longPoll方法。当然,执行error方法有一定的次数限制,由变量transmission_errorsx控制。

大家可能会有一个疑问,这样一直递归循环获取数据,服务器会不会有很大的负担?在没有数据可获取的时候,也会一直这样循环吗?当然,答案时否定的!并且,nodejs利用自身的特点,很好的处理了这个问题。接着往下看:
Server.js
现看server中如何回应上面client的调用,核心代码:

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()是什么意思,它和本次教程无关。总之知道它能回应client的调用就行了。上面的代码,除了对session的一些操作之外,只是调用了channel的query方法。注意传递的参数:
since,它纪录了一个时间;
匿名方法,它接受一个messages参数,两个动作:1 更新session时间,2 返回一个json,即把messages返回给客户端。

有人可能会有疑问:在这里直接返回messages不行吗,干嘛还得在一个channel中定义一个方法才操作?答案:如果是那样,就成了一个死循环,server和client每时每刻都进行着数据交互,即使没有信息可返回。

还是接着往下看吧!

看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() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值 
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秒执行一次的setInterval函数。

首先看query方法,

query方法接收两个参数:
since:纪录一个时间
callback:即上面讲调用channel.query方法时传入的那个匿名函数(JS中,函数可以当参数传递,接收之后可直接调用。不会赶快补课啊。。。)

messages里存的时当前的聊天纪录队列,query方法会查找符合条件的聊天纪录,把他们放在matching队列中。如果matching.length>0,则调用callback接收的函数,即把matching以json格式返回client。但是。。。接下来是重点!!!

if (matching.length != 0) { 
callback(matching); 
} else { 
callbacks.push({ timestamp: new Date(), callback: callback }); 
}

如果matching.length<=0,程序会把callback和当前时间,以json格式,存入一个callbacks队列中。对!不执行了,因为没有符合条件的聊天消息,不需要在client显示,所以先把这个函数存起来,先不执行。

那也不能这样一直存着啊,万一下一秒有人发聊天消息怎么办?

接着看appendMessage(添加聊天消息)方法:

该方法中,前段部分很好理解,无非是接收传入的参数,组合成一个m集合,然后用sys.puts在终端显示一下,再把m插入到messages聊天消息队列中。接下来又是重点:

while (callbacks.length > 0) { 
//shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值 
callbacks.shift().callback([m]); 
}

现在要判断callbacks有没有储存,如果有,就执行一个,删除一个,知道执行完了为止。因为之前在没有聊天消息可返回的时候,有人发出了请求,然后系统没有执行这些请求,都把他们放在callbacks列表中了。

现在有人发送了聊天消息,执行添加方法的时候,要再次把那些没执行的请求都执行一遍。

通俗理解可以是:你给我发送请求了,我现在还没有新消息可以答复你,一旦有人发了新消息,我会立刻答复你。

不知道大家理解了没。。。

这一步完了,最后一步,是每隔3秒钟,清空过期的callbacks,是一个setInterval函数,这个不难理解。
总结

nodejs用自身基于事件驱动的语言特点,实现了长链接刷新功能,让我们打开眼界。自我感觉受益匪浅。特此花时间写个教程跟大家分享,同时也加深我自己的理解。

Javascript 相关文章推荐
JavaScript confirm选择判断
Oct 18 Javascript
JQ技术实现注册页面带有校验密码强度
Jul 27 Javascript
Jquery Easyui自定义下拉框组件使用详解(21)
Dec 31 Javascript
详解windows下vue-cli及webpack 构建网站(三)使用组件
Jun 17 Javascript
详解基于webpack2.x的vue2.x的多页面站点
Aug 21 Javascript
Node.js中使用mongoose操作mongodb数据库的方法
Sep 12 Javascript
vue.js模仿京东省市区三级联动的选择组件实例代码
Nov 22 Javascript
基于vue-ssr服务端渲染入门详解
Jan 08 Javascript
使用vue-route 的 beforeEach 实现导航守卫(路由跳转前验证登录)功能
Mar 22 Javascript
Angular学习笔记之集成三方UI框架、控件的示例
Mar 23 Javascript
微信公众平台 发送模板消息(Java接口开发)
Apr 17 Javascript
java遇到微信小程序 &quot;支付验证签名失败&quot; 问题解决
Dec 22 Javascript
Jquery弹出窗口插件 LeanModal的使用方法
Mar 10 #Javascript
解决3.01版的jquery.form.js中文乱码问题的解决方法
Mar 08 #Javascript
Node.js实战 建立简单的Web服务器
Mar 08 #Javascript
使用UglifyJS合并/压缩JavaScript的方法
Mar 07 #Javascript
Uglifyjs(JS代码优化工具)入门 安装使用
Apr 13 #Javascript
node.js 一个简单的页面输出实现代码
Mar 07 #Javascript
服务器端的JavaScript脚本 Node.js 使用入门
Mar 07 #Javascript
You might like
Excel数据导入Mysql数据库的实现代码
2008/06/05 PHP
Nigma vs Liquid BO3 第二场2.13
2021/03/10 DOTA
javascript处理table表格的代码
2010/12/06 Javascript
jqeury eval将字符串转换json的方法
2011/01/20 Javascript
JS+flash实现chrome和ie浏览器下同时可以复制粘贴
2013/09/22 Javascript
js replace替换所有匹配的字符串
2014/02/13 Javascript
JavaScript实现select添加option
2015/07/03 Javascript
javascript基础知识分享之类与函数化
2016/02/13 Javascript
JQuery ztree 异步加载实例讲解
2016/02/25 Javascript
JS实现图片局部放大或缩小的方法
2016/08/20 Javascript
Vue2实现组件props双向绑定
2016/12/02 Javascript
Javascript Function.prototype.bind详细分析
2016/12/29 Javascript
webpack入门+react环境配置
2017/02/08 Javascript
js oncontextmenu事件使用详解
2017/03/25 Javascript
微信小程序实现点击按钮修改view标签背景颜色功能示例【附demo源码下载】
2017/12/06 Javascript
jQuery中图片展示插件highslide.js的简单dom
2018/04/22 jQuery
微信小程序实现通过双向滑动缩放图片大小的方法
2018/12/30 Javascript
基于 jQuery 实现键盘事件监听控件
2019/04/04 jQuery
浅谈VUE中演示v-for为什么要加key
2020/01/16 Javascript
vue+ts下对axios的封装实现
2020/02/18 Javascript
js 函数性能比较方法
2020/08/24 Javascript
python实现进程间通信简单实例
2014/07/23 Python
Python编程中time模块的一些关键用法解析
2016/01/19 Python
Python简单检测文本类型的2种方法【基于文件头及cchardet库】
2016/09/18 Python
wxPython的安装与使用教程
2018/08/31 Python
俄罗斯天然和有机产品、健康生活网上商店:Fitomarket.ru
2020/10/09 全球购物
编程实现当输入某产品代码则打印出该产品记录的功能
2014/05/03 面试题
交通安全演讲稿
2014/01/07 职场文书
老师给学生的表扬信
2014/01/17 职场文书
授权委托书格式范文
2014/08/02 职场文书
党支部特色活动方案
2014/08/20 职场文书
群众路线教育实践活动自我剖析思想汇报
2014/10/04 职场文书
2015年社区关工委工作总结
2015/04/03 职场文书
早会开场白台词大全
2015/06/01 职场文书
高中班主任培训心得体会
2016/01/07 职场文书
zabbix agent2 监控oracle数据库的方法
2021/05/13 Oracle