comet 长轮询与 node 实现
介绍
comet和ajax都是为了解决HTTP请求中存在的一些问题,comet跟ajax不同的地方在于,ajax是主动’拉’服务端的内容,而comet是服务端主动’推’内容给客户端。实现成本及其简单,比起ajax模拟的 间隔一段去查询服务端内容的方式在性能等各方面都要好。
有关 comet 和 socket 对比的资料见 Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
优点
- 实现很简单。
- 实时性好,消息延迟小。
缺点
- 只能允许服务端推送,客户端中途无法再发消息给服务端。不像 websocket,可以双向的发送消息。
- 很适合小数据传输,但是不适合大数据。服务端数据变更频繁的话,这种机制和定时轮询比起来没有本质的提高。
- 没有数据到达时,http 连接会停留一段时间,这会造成服务器资源浪费。设有 1000 个人停留在某个客户端页面,等待 server 端的数据更新,那就很有可能服务器这边挂着 1000 个线程,在不停检测数据是否发生变化。
不管是长轮询还是短轮询,都不太适用于客户端数量太多的情况,每个服务器所能承载的TCP连接数是有上限的,这种轮询很容易把连接数占光。
而 websocket 无需循环等待(长轮询),CPU和内存资源不以客户端数量衡量,而是以客户端事件数衡量。是三种方式里性能最佳的。
用途
- 聊天室
- 股票
代码
前端部分 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>H5原生的comet长连接</title>
<style>
body {
width:640px;
margin:20px 30px;
}
#status {
width:10px;
height: 10px;
border-radius: 50%;
position: absolute;
top:15px;
left: 0;
margin:10px;
background: #999;
}
#status.working {
-webkit-animation: flash 1s infinite;
-webkit-animation-direction: alternate;
}
#msg {
max-height: 300px;
overflow: auto;
border-radius: 5px;
border:1px solid #ccc;
margin:20px 0;
padding: 5px 15px;
background: #eee;
}
#msg li {
margin:5px 20px;
}
@-webkit-keyframes flash {
from {
box-shadow: 0 0 5px green;
background: green;
}
to {
box-shadow: 0 0 12px green;
background: green;
}
}
</style>
</head>
<body>
<p>用node.js实现HTML5原生的comet(长连接)</p>
<div id="status"></div>
<button id="switch">开启</button>
<button id="clean">清空</button>
<ol id="msg"></ol>
<script>
var Btn = function () {
this._switch = document.querySelector('#switch');
this._clean = document.querySelector('#clean');
this._msg = document.querySelector('#msg');
this._status = document.querySelector('#status');
this.es = null;
this.init();
};
Btn.prototype.init = function () {
var that = this;
var _msg = that._msg;
that._clean.addEventListener('click', function () {
_msg.innerHTML = '';
});
that._switch.addEventListener('click', function () {
if(this.innerText === '开启') {
that.on();
} else {
that.off();
}
});
that.on();
};
Btn.prototype.on = function () {
var that = this;
// 1. 声明EventSource
that.es = new EventSource('/msg');
// 2. 监听数据
that.es.onmessage = function (e) {
document.querySelector('#msg').innerHTML += '<li>'+ e.data +'</li>'
};
that._switch.innerText = '关掉';
that._status.classList.add('working');
};
Btn.prototype.off = function () {
this.es.close();
this._switch.innerText = '开启';
this._status.classList.remove('working');
};
new Btn();
</script>
</body>
</html>
node 部分,index.js
var http = require('http');
var fs = require('fs');
var url = require('url');
var port = process.argv[2] || 3000;
http.createServer(function (req, res) {
var pathname = url.parse(req.url).pathname;
if(pathname === '/msg') {
// 1. 设定头信息
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// 2. 输出内容,必须 "data:" 开头 "\n\n" 结尾(代表结束)
setInterval(function () {
res.write('data: ' + Date.now() + '\n\n');
}, 1000);
} else if (pathname === '/index.html' || pathname === '/client.html') {
// 其他请求显示index.html
fs.readFile('./index.html', function (err, content) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(content, 'utf-8');
});
} else {
res.end('');
}
}).listen(port);
console.log('Server running on port ' + port);
启动后访问 localhost:3000/index.html 就可以看到
node index.js