一文领会服务端推送(含JS代码示例)

常用的服务端推送手艺,包罗轮询、长轮询、websocket、server-sent-event(SSE)

传统的HTTP请求是由客户端发送一个request,服务端返回对应response,以是当服务端想自动给客户端发送新闻时就遇到了问题。常见的营业场景如新新闻提醒。

1、轮询(Polling)

最简朴的方式是轮询,即客户端不停的发送请求来获取最新的新闻。优点是实现简朴。瑕玷是请求中有泰半是无用,虚耗带宽和服务器资源,同时,凭据轮询的时间距离差别,获取新闻会有对应的延迟。

实例,新浪微博新新闻提醒。打开控制台可以发现 https://rm.api.weibo.com/2/remind/push_count.json 一个 jsonp 请求,这个请求每隔 30s 发送一次,每次需求 100ms 左右。

一文领会服务端推送(含JS代码示例)

2、长轮询(Long Polling)

长轮询也对照容易明白,就是前端提议请求,并设置一个对照长的超时时间,后端接收到请求后,若是没有相关数据,会hold住请求直到有效果了,或者守候一定时间超时才返回。返回后,客户端会立刻提议下一次请求。长轮询的控制权的服务器端,泛起相关数据后会立刻返回,实时性较高。

实例,QQ邮箱的新新闻提醒。可以看到 https://wp.mail.qq.com/poll 请求不停发送, 没有新新闻时,请求每次都市需要 30s,上一次请求返回后立刻发送下一次请求,而当服务端有新新闻时会立刻返回,实时性较高。

一文领会服务端推送(含JS代码示例)

用代码简朴实现以上两种轮询

服务端代码

const express = require('express');
const port = 2333;
const app = express();

app.get('/start', start);
app.get('/getCurrentResult', getCurrentResult);
app.get('/getFinalResult', getFinalResult);

app.listen(port, () => console.log(`Server listening on port ${port}`));

// 最先一个义务
function start(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*'); // 允许跨域
    _startTask();
    res.json({
       code: 0,
       data: '最先义务'
    });
}
// 返回实时效果
function getCurrentResult(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.json({
        code: 0,
        data: result
    });
}
// 义务运行竣事之后再返回运行效果
async function getFinalResult(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    let result = await _startTask();
    res.json({
        code: 0,
        data: result
    });
}

// 模拟执行一个义务
let result = null;
function _startTask() {
    result = null;
    return new Promise((res, rej) => {
        // 义务需要10s 10s后获得result
        setTimeout(() => {
            result = 'hello world';
            res(result);
        }, 10000);
    });
}

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>轮询&长轮询</title>
</head>
<body>
    <button onclick="start(1)">最先义务 轮询</button>
    <button onclick="start(2)">最先义务 长轮询</button>
    <div id="hint"></div>
    <script>
        function start(type) {
            console.log('===start===');
            fetch('http://localhost:2333/start').then(res => {
                return res.json();
            }).then(function(data){
                console.log(data);
                setHint('义务执行中...');
                type == 1 ? loop() : longPolling();
            }).catch(function(err){
                console.error(err);
            });
        }

        function loop() {
            fetch('http://localhost:2333/getCurrentResult').then(res => {
                return res.json();
            }).then(function(data){
                console.log(data);
                if (!data.data) {
                    setTimeout(loop, 1000); // 1s轮询一次
                } else {
                    setHint('执行乐成 效果 = ' + data.data);
                }
            }).catch(function(err){
                console.error(err);
            });
        }

        function longPolling() {
            fetch('http://localhost:2333/getFinalResult').then(res => {
                return res.json();
            }).then(function(data){
                console.log(data);
                setHint('执行乐成 效果 = ' + data.data);
            }).catch(function(err){
                console.error(err);
            });
        }

        function setHint(text) {
            hint.innerHTML = text;
        }
    </script>
</body>
</html>

3、WebSocket

上面两种方式,现实上照样客户端单向发送新闻,而 WebSocket 本质上解决了这个问题,WebSocket 是 HTML5 最先提供的一种在单个 TCP 毗邻上举行全双工通讯的协议。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以建立持久性的毗邻,并举行双向数据传输。

WebSocket 握手阶段接纳 HTTP 协议,客户端浏览器首先要向服务器提议一个 HTTP 请求,其中附加头信息 Upgrade: WebSocket 解释这是一个申请协议升级的 HTTP 请求,服务器端剖析这些附加的头信息然后发生应答信息返回给客户端,客户端和服务器端的 WebSocket 毗邻就建立起来了,双方就可以通过这个毗邻通道自由的传递信息,而且这个毗邻会连续存在直到客户端或者服务器端的某一方自动的关闭毗邻。WebSocket 没有同源限制。

Java 内存模型都不会,就敢在简历上写熟悉并发编程吗

实例,LeetCode-CN 的新新闻提醒,预测是通过 WebSocket 实时返回是否有新新闻,再通过 XHR 请求详细信息。

一文领会服务端推送(含JS代码示例)

简朴代码实现,使用了 ws 包

服务端代码

const WebSocket = require('ws');
const http = require('http');

const port = 2333;

const server = http.createServer();
const wss = new WebSocket.Server({ server, path: '/ws' });

wss.on('connection', function(ws) {
    console.log('WebSocket connection established');
    let progress = 0;

    ws.send(`义务进度 -- ${progress}%`);
    let timer = setInterval(() => {
        // 推送义务完成进度
        if (++progress % 10 == 0) {
            ws.send(`义务进度 -- ${progress}%`);
        }
        if (progress == 100) {
            clearInterval(timer);
            ws.close();
        }
    }, 200);

    ws.on('close', () => {
        console.log('WebSocket connection closed');
        clearInterval(timer);
    });
});

server.listen(port, function() {
    console.log(`Server listening on port ${port}`);
});

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>WebSocket</title>
</head>
<body>
    <button onclick="start()">最先义务</button>
    <div id="hint" style="white-space: pre-line;"></div>
    <script>
        let ws;
        function start() {
            if (ws) ws.close();

            console.log('===start===');
            
            ws = new WebSocket('ws://localhost:2333/ws');
            ws.onmessage = function(ev) {
                let data = ev.data;
                console.log(data);
                showMessage(data);
            }
            ws.onerror = function() {
                console.log('WebSocket error');
            };
            ws.onopen = function() {
                console.log('WebSocket connection established');
            };
            ws.onclose = function() {
                console.log('WebSocket connection closed');
                ws = null;
            };
        }

        function showMessage(text) {
            hint.innerHTML = hint.innerHTML + '\n' + text;
        }
    </script>
</body>
</html>

4、Sever-Sent Event(SSE)

SSE 是一种能让浏览器通过 HTTP 毗邻自动收到服务器端推送的手艺,EventSource 是 浏览器提供的对应 API。通过 EventSource 实例打开与 HTTP 服务器的持久毗邻,该服务器以文本/事宜流花样发送事宜,毗邻会保持打开状态,直到服务端或客户端自动关闭。

与 WebSocket 区别,SSE 基于 HTTP 协议,使用简朴,SSE 默认支持断线重连,然则 SSE 只能由服务端向客户端推动新闻。

SSE 有四种字段,其他的字段会被忽略。字段之间用\n 分开,每条新闻要以 \n\n 末端。

data    // 数据项
event   // 事宜项 默以为 message 可设置随便值
id       // 数据标识符,用于断线重连
retry   // 断线后重连时间

现实应用……最近几天已经养成了进入一个网站就打开控制台看 network 的习惯……不外照样没有找到 SSE 的现实应用……

简朴代码实现:

服务端代码

const express = require('express');
const port = 2333;
const app = express();

app.get('/sse', respondSSE);

function respondSSE(req, res) {
    let msg = 0;
    let timer;
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Connection': 'keep-alive',
        'Access-Control-Allow-Origin': '*'
    });

    res.write(sseMsg({
        data: '===start===',
        // 默认 event 是 'message'
    }));

    timer = setInterval(() => {
        res.write(sseMsg({
            id: Date.now(),
            event: 'custom-event',
            data: msg++,
            retry: 2000
        }));
    }, 1000);

    res.on('close', function () {
        clearInterval(timer);
        console.log('SSE connection closed');
    });
}

const sseMsg = (sseObj) => {
    let fields = ['id', 'event', 'data', 'retry'];
    return fields
        .filter(f => sseObj[f] != null)
        .map(f => f + ':' + sseObj[f]).join('\n') + '\n\n';
}

app.listen(port, () => console.log(`Server listening on port ${port}`));

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button onclick="start()">最先</button>
    <button onclick="over()">竣事</button>
    <div id="hint"></div>
    <script>
        let source;
        function start() {
            source = new EventSource('http://localhost:2333/sse');
            source.addEventListener('open', () => {
                console.log('SSE connection established');
            }, false);

            source.addEventListener('message', e => {
                console.log(e.data);
            }, false);

            source.addEventListener('custom-event', e => {
                console.log('custom-event data: ', e.data);
                showMessage('新新闻: ' + e.data + ' 条');
            }, false);
        }

        function over() {
            source.close();
        }

        function showMessage(text) {
            hint.innerHTML = text;
        }
    </script>
</body>
</html>

5、HTTP/2 Server Push

文章对照少,而且和上面的推送并不一样,看到这篇讲得不错~

Node HTTP/2 Server Push 从领会到放弃

参考资料

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/1915.html