如何使用Node.js搭建最简单的comet原型
沉沙 2018-06-27 来源 : 阅读 483 评论 0

摘要:Comet, 是基于HTTP长连接的“服务器推”技术.。和AJAX类似,,这是一种改善WEB用户体验的通讯技术。希望阅读本篇文章以后大家有所收获,帮助大家对Node.js的理解更加深入。

什么是Comet

Comet, 是基于HTTP长连接的“服务器推”技术. 和AJAX类似, 这是一种改善WEB用户体验的通讯技术. 其实早在CGI盛行的时代, 有种叫做"Server-Push"的技术, 和Comet本质是一回事, 都是基于长连接来实现. Server-Push更具体, 强调使用multipart/x-mixed-replace的Conent-Type技巧, 使得服务器能替换浏览器的内容. Comet包含面更广泛, 只要是有长连接和HTTP chunked的实现, 都算作其中. 这篇文章详细介绍了Comet的各种形态,值得一读.

Comet虽然能让浏览器达到及时的响应, 但是由于基于长连接实现, 服务器成本很高. 最近这种技术之所以火起来, 主要还是牛人们探索到了各种降低服务器成本的方法. 这个叫amix的家伙对此有较多的研究.

什么是NodeJs

nodejs号称Evented I/O for V8 JavaScript, 是基于V8的一款神器, 让我们可以使用javascript轻松进行服务器端编程.

最简单的Comet原型

我用一下午的时间, 使用nodejs搞了一个简单的不能再简单的Comet原型. 在这个demo里面, 我假定使用iframe实现Comet, 但是忽略了iframe的父窗口和客户端js库, 只考虑服务器如何将HTTP chunked push到客户端.

我定义了一种Comet资源: //{host}/{pathname}?[{query_string}] . 其中{pathname}直接当作客户端id来使用(在程序里面它被叫做resid). {query_string}用来做消息内容. 这样, 原型就简化成了两种操作:

HTTP GET : //{host}/{pathname} 用来模拟iframe长连接, 不断接收到新数据.

HTTP PUT : //{host}/{pathname}?{query_string} 用来模拟业务操作, 直接将{query_string}当作数据投递到上面的长连接里面.

具体实现

好, 主角登场, 用nodejs实现最简单的Comet:

1. global.messages = {  
2.     //'resid':[]  
3. };  
4. var char500 = (function(){ var i=0; var arr = []; for(i=0; i<500; i++) { arr.push( ' ' ); } return arr.join(''); })();  
5. var http_method_funs = {  
6.     'GET': function(resid, data, request, response) {  
7.         if(global.messages[resid] == undefined) {  
8.             global.messages[resid] = [];  
9.         }  
10.         response.writeHead(200, {'content-type': 'text/plain'});  
11.         var interval = setInterval(myoutput, 500 );  
12.         response.connection.on('end', function(){  
13.             console.log("GET\t" + resid + "\tclosed");  
14.         clearInterval(interval);  
15.                 });  
16.         myoutput();  
17. function myoutput(){  
18.             var msgs = global.messages[resid];  
19.             if(msgs.length){  
20.                 var str = msgs.join("\n\n\n") + "\n\n\n";  
21.                 str = (str.length < 500 ) ? ( str + char500 ) : str; //for MTU  
22.             response.write(str);  
23.                 global.messages[resid] = [];  
24.  
25.             }  
26.     }  
27.     },  
28.     'PUT': function(resid, data , request, response) {  
29.         if(global.messages[resid] == undefined) {  
30.             global.messages[resid] = [];  
31.         }  
32.     global.messages[resid].push(data);  
33.         console.log(global.messages);  
34.     response.writeHead(200, {'content-type': 'text/plain'});  
35.         response.end( 'ok\n');  
36.     },  
37. };  
38. //method function   
39. require('http').createServer(function (request, response) {  
40.         var urlinfo = require('url').parse(request.url);  
41.         var resid = urlinfo['pathname'];  
42.         var data = (urlinfo['query']) ? urlinfo['query'] : 0 ;  
43.         var method = request.method;  
44.         console.log(method + "\t" + resid );  
45.         if(typeof http_method_funs[method] == 'function') {  
46.         http_method_funs[method].call(null, resid, data, request, response);  
47.         }  
48.         else {  
49.             response.writeHead(400);  
50.         response.end("unsupport method\n");  
51.  
52.         }  
53.         }).listen(18124);  
54. console.log('server running at //127.0.0.1:18124/');

测试方法

上面的代码保存到文件, 我们在第一个终端启动这个服务:

shell> node hello.js 

我们在第二个终端模拟iframe的数据流.输入命令, 观察收到的数据:

1. telnet 127.0.0.1 18124  
2. GET /mymessages HTTP/1.1  
3. HTTP/1.1 200 OK  
4. content-type: text/plain  
5. Connection: keep-alive  
6. Transfer-Encoding: chunked

我们在第三个终端输入curl -X PUT命令, 模拟发送两条消息:

1. shell> curl -X PUT "//127.0.0.1:18124/mymessages?a=1&b=2&c=3" 
2. ok  
3. shell> curl -X PUT "//127.0.0.1:18124/mymessages?a=4&b=5&c=6" 
4. ok

观察第二个终端, 会发现已经收到两条HTTP chunked. (为了避免测试数据小于MTU, 我实际上多输出了一些空格,但这里省去了.)

1. 202  
2. a=1&b=2&c=3  
3. 202  
4. a=4&b=5&c=6

总结

在这个原型中, 我省掉了Comet iframe方案内无关紧要的东西, 只用HTTP PUT/GET来演示一个最简单的原型. 用NodeJs轻松搭建了它.

可以看到, 用javascript event的风格写服务器, 简直是明白如话, 散文那样自然.

我用global.messages对象来存储消息, key是resid(上面说的客户端id), value是个array, 里面存储客户端收到的messages.

我为GET/PUT两种操作分别实现了两个函数.

PUT函数, 收到请求就将query_string当作message存到对应resid的array中, 然后断开HTTP连接.

GET函数, 收到请求就启动一个定时器, 轮询global.messages里面自己的消息队列(array). 如果遇到数据则在HTTP response输出http chunked. HTTP连接不主动关闭, 但如果被异常关闭则清除定时器对象.

就这么一个简单功能, 如果用C和select来开发, 那么一个全局的客户端句柄队列是免不了要实现的, 当io事件到来时, 如何恢复之前中断的上下文,进行正确的io操作, 也是一件头疼的事情.

而我们看这个实现里面的myoutput定时器函数. 由于局部变量resid,response在函数的定义时环境内, 所以函数被执行时, 很自然就使用这些上下文信息. 相比来说, C的实现里面专门为此设计一个客户端句柄队列就太突兀了.

javascript通过函数式和闭包, 轻而易举的完成了一个非阻塞服务器. 如果说libevent是通过库来实现了事件的封装, 那么nodejs所宣称的"Evented I/O for V8 JavaScript", 则是借语言本身的优雅特性获得自然的收获.

 

本文由职坐标整理并发布,了解更多内容,请关注职坐标WEB前端Node.js频道!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程