概述

  • comet
  • websocket
  • sse

1. comet

之前参与项目里,见到过有人用 ajax 长轮询来做实时数据推送。但是这样做有一个弊端就是会建立很多 TCP 连接,这样会给系统带来比较大的 IO 负担。

有没有一种方式,我们只进行一次 TCP 连接,在这一次 TCP 连接中,服务器不断给客户端吐数据。下面我们就来介绍一下 comet 推送方式:

我们用 php 来做服务端

<?php
    header("Content-type:appliacation/json;charset=utf-8");
    header("Cache-Control:max-age=0");  // 让前端接收没有缓冲,因为我们要实时获取数据

    // 我们用一种方式,让它连上之后不释放
    $i=0;
    while($i<9){
        $i++;
        $radom = rand(1,999);
        sleep(1);
        echo $radom;
        echo "<br>";
        ob_flush(); // 把当前资源释放掉
        flush();    // 拿到释放掉的资源,吐浏览器
        // 一直让它输出,而且是一个流式输出
    }
?>

js 怎样捕捉一直在发送阶段

<script>
    var getXmlHttpRequest = function(){
        if(window.XMLHttpRequest){
            return new XMLHttpRequest();
        }
        else if(window.ActiveXObject){
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
    var xhr = getXmlHttpRequest();
    xhr.onreadystatechange = function (){
        console.log(xhr.readyState);
        // 检测readyState为3的时候,我们就能把responseText输出出来
        // 而且每次一都能把上次全部内容都打印出来
        if(xhr.readyState ===3 &&xhr.status === 200){
            console.log(xhr.responseText);
        }
    }
    xhr.open("get","data.php",true);
    xhr.send("");
</script>

js 用普通 ajax 去请求

<script>
    function conn(){
        $.ajax({
            url: 'data.php',
            dataType: 'json',
            success: function(data){
                console.log(data);
                conn();
            }
        })
    }
    conn();
</script>

服务端我们不去断掉连接

<?php
    header("Content-type:appliacation/json;charset=utf-8");
    header("Cache-Control:max-age=0");
    sleep(1);
    $res = array('success'=>'ok','test'=>'我是测试文本');
    echo json_encode($res);
?>

或者我们用下面这种方式,用一个 while 循环,我们可以通过前端给来的参数去判断执行过少次。

<?php
    header("Content-type:appliacation/json;charset=utf-8");
    header("Cache-Control:max-age=0");
    while(true){
        sleep(1);
        $res = array('success'=>'ok','test'=>'我是测试文本');
        echo json_encode($res);
        exit(); // 切记要退出,不然前端拿不到数据
    }
?>

因为只要不断掉,我们再去请求还是连接的上一次请求,只不过这是用前端实现了轮询效果,刚才我们用后端 flush()来实现的这件事

2. websocket

websocket 它是一种长连接,通过 websocket 我们能实现后端向前端推送数据,前端也可以向后端推送数据。这里我们主要讲前端 H5 websocket 怎样和 nodejs 配合。

为什么要用到 socket.io 呢?

因为它承载了 socket 大部分功能,而且相对稳定

服务器端

  • 第一步我们建一个 express 项目
$ npm install express --save
$ npm install -g express-generator
express
  • 第二步我们要在项目文件里安装 socket.io
$ npm install socket.io --save
  • 第三步我们在 node_module 里面找到 socket.io.js 这个文件,这是给前端用的
    • 文件在 node_modules/socket.io-client/dist/socket.io.js

接下来我们在 app.js 中引入 socket.io

var express = require('express');
var path = require('path');

var app = express();

var http = require('http');
var server = http.createServer(app);
var io = require('socket.io').listen(server);

app.set('port', 3000);
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(express.static(path.join(__dirname, 'public')));

io.on('connection', function(socket) {
  socket.emit('open'); // 通知客户端已连接

  // 对message事件的监听
  socket.on('message', function(msg) {
    console.log('this is msg:', msg);
    socket.emit('test', 'server ready');
  });

  // 监听退出事件
  socket.on('disconnect', function() {});
})

app.get('/', function(req, res) {
  res.sendfile('views/index.html');
});

server.listen(app.get('port'), function() {
  console.log("socket server listen" + app.get('port'));
});

在 views 文件夹里建 index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  hello
  <script src="javascripts/socket.io.js"></script>
  <script src="javascripts/main.js"></script>
</body>
</html>

在静态文件 javascripts 文件夹下建 main.js

(function() {
  var i = 0;
  // 建立websocket连接
  var socket = io.connect("http://localhost:3000");
  // 收到server的连接确认
  socket.on('open', function() {
    console.log('已连接');
    socket.send("ok");
  })
  socket.on('test', function(json) {
    console.log('test', json);
  })
})();

具体步骤:

  • 在 app.js 文件中用 socket.io 监听 server 端口
  • 在 main.js 文件中建立 websocket 连接
  • app.js 会在 connect 事件中监听到连接,并触发 open(自己定义的)事件
  • 此时前端监听到 open 事件
  • 同时前端也可以用 socket 连接用 send 方法向后端推送消息
  • 后端会在 message 事件中监听到前端推送过来的消息

参考资料:socket.io 官网 api

3. sse

Server-Sent Events(简称 SSE)

SSE 是一种能让浏览器通过 http 连接自动收到服务器端更新的技术,SSE EventSource 接口被 W3C 制定为 HTML5 的一部分。

它是能完成服务器端向客户端单向推送消息,但是 IE 不支持

服务器端

  • php 代码
<?php
    // 以event-steam的方式输出
    header("Content-Type:text/event-stream;charset='utf-8'");
    // 指定哪个域来访问
    header("Access-Control-Allow-Origin:http://127.0.0.1/");
    echo "data:现在北京时间是".date('H:i:s')."\r\n\r\n";
?>
  • html 代码
<script>
var source;

function init(argument) {
  source = new EventSource('http://localhost/sse/data.php');
  // 建立连接
  source.onopen = function() {
    console.log('连接已建立', this.readyState)
  }
  // 后端实时推送的数据
  source.onmessage = function(event) {
    console.log('从服务器实时获取的数据', event.data);
  }
  // 出错的监听
  source.onerror = function() {

  }
}
init();
</script>

这里主要是 new 一个 EventSource 实例,然后监听 onopen 事件,这个事件是监听连接已建立,然后通过 onmessage 事件来监听后端推送过来的消息。