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

参考

Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

用node.js实现HTML5原生的comet(长连接)

posted @ 2020-04-21 10:11  Ever-Lose  阅读(574)  评论(0编辑  收藏  举报