我们经常会看到一些网站可以实时的向我们的页面推送一些信息,比如网页版的聊天、或者一些社交网站上的消息推送等等。那么怎样才能做到呢?我提供一种方法,不一定是最优解,但能实现基本的需求。
首先我们必须说明一点:要有一些后端的知识,因为这次我们要同时写前、后端。
我下面就以nodeJS为例,当然其他的后端实现方法也是可以的,基本原理是一样的。
第一步:确定思路。
页面加载后向后台发送一个Ajax请求(如果对Ajax还不了解可以到W3C上学习一下,上面有很棒的讲解),作为长连接的发起。当后端收到请求后,延时处理20秒后再回应前端页面。前台收到后端响应后就立即发起下一次请求,这样无限循环下去。就完成了长连接的基本功能。下一步我们先实现一下,完成后我们在对它进行优化。
前端:
在你的ready()里面添加如下代码:
function longLink(){ var data = { //'type': 'longLink' }; $.post('/longLink', { data: data, "_csrf": token }, function (result) { //console.log('long link data: ', result); longLink(); }); } longLink();
我们这里使用的是POST请求。既然我们向后台发了请求,后台哪有让老朋友吃闭门羹的道理!
后端:
用你喜欢的方式(express或kraken等等都可以)创建nodejs工程,下面在route或controller中添加你的路由处理逻辑
1 router.post('/longLink', function (req, res) { 2 var data = req.body.data; 3 var curRes = res; 4 5 //20秒定时链接 并告知浏览器无openid 6 var longLinkTimeCtl = setTimeout(function(){ 7 curRes.send({ 8 code: 0, 9 detail:{ 10 withData: false 11 } 12 }); 13 }, 20000); 14 15 });
这样就有了一个基本的长连接。后端在req.body中拿到前端发来的数据然后再延时响应。20秒的时间可以根据自己的需求修改,如果有对前端的通知可以夹杂在send中的对象内。
进阶:
看到nodejs(后端)send中的withData:false了吗?作为“例行公事”的响应中,当然不会有数据啦,那么仅仅是不断循环的请求--响应,又有什么意义呢?换句话说,什么时候发送携带数据的响应呢?
当我们的后端收到某个数据(其他的请求或有新来的数据,总之就是一个需要后端马上响应前端的时机)的时候,我们就要打断原有的延时(clearTimeout),立即响应并携带信息。下面我们假设这一时机是一个事件监听:
router.post('/longLink', function (req, res) { var data = req.body.data; var curRes = res; //20秒定时链接 并告知浏览器无openid var longLinkTimeCtl = setTimeout(function(){ curRes.send({ code: 0, detail:{ withData: false } }); //解绑 防止多次绑定 global.event_getData.removeListener('getData', getDataCallback); }, 20000); // 接到 获取openid的事件后 打断20秒定时 立即响应浏览器,并携带openid var getDataCallback = function(data){ clearTimeout(longLinkTimeCtl); if(data.xxx){ var xxx= data.xxx; curRes.send({ code: 0, detail:{ withData: true, data:{ xxx: xxx } } }); } //解绑 防止多次绑定 global.event_getData.removeListener('getData', getDataCallback); } global.event_getData.on('getData', getDataCallback); });
假设服务器的某个逻辑收到了某个数据,碰巧我们的需求中要求我们必须马上把这个数据发送到前端页面,就可以在拿到数据的逻辑处触发一个全局的事件(至于nodejs中如何处理事件,我将在下一篇文章中简单的分享一下),这里你只要知道我们可以向jquery中那样用on()来监听。我的例子中事件的回调函数名为getDataCallback,记住这个名字,一会我们还要解除绑定。按照getDataCallback中逻辑,我们要在接到数据后清除延时而马上把消息send给页面。我想细心的你一定会发现下面这句是干什么的?而且还用了两次。
global.event_getData.removeListener('getData', getDataCallback);
由于在长连接中前端页面一直在不断的向后端发请求,on()的绑定也会绑定多次,我们需要解绑来保证只绑定了一个,以防多次响应。我想一定会有人问我为什么不直接在绑定之前解绑,而是在两个出口解绑?原因是:每一次进入路由后,我们就和上一次进入的不是同一个空间了,所以在绑定之前解绑并不能达到预期的效果,回调依然会触发多次。我们在两个出口解绑,可以在当前的空间解绑。
当我们发送的响应中携带了数据而且withData: true,剩下的工作就是前端页面了。
1 function longLink(){ 2 var data = { 3 //'type': 'longLink' 4 }; 5 $.post('/longLink', { 6 data: data, 7 "_csrf": token 8 }, function (result) { 9 //console.log('long link data: ', result); 10 longLink(); 11 if(result.code == 0){ 12 //判断是否携带了信息 13 if(result.detail.withData){ 14 yyyyyyyyyyyy(result.detail.data); 15 } 16 } 17 }); 18 } 19 20 longLink();
这里yyyyyyyyyyyy是处理数据的程序。
小结:
到这里一个简单的长连接就完成了。