数据实时更新--webSocket/轮询
一. 基本概念
单工:单向通信。即只能服务器->客户端。例如: UDP协议
半双工:既可以服务器->客户端,也可以客户端->服务器。但是同一时间,只能是一个方向。例如: http协议。
全双工:双向通信。同一时间内既可以客户端->服务器;也可以服务器->客户端。例如:webSocket协议
二. 双向通信
1. 轮询
轮询即周期性(setInterval)的发起请求并返回响应(发送请求->响应数据->关闭连接->发送请求)。
如果响应的所需时间过长,它的再次请求也不会等待上一次的响应完成。
缺点:
1)请求过于频繁,请求数过多,每次请求都要携带请求头,浪费流量,消耗CPU的利用率
2)当客户端过多时,会导致并发数量过高
3)后台数据不频繁变化时, 频繁发出的很多请求是无效请求
示例:
客户端:
<!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> <div id="root"></div> <script> const xhr = new XMLHttpRequest(); setInterval(function() { xhr.open('GET', '/clock', true); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { window.root.innerHTML = xhr.response; } } xhr.send(); }, 1000); </script> </body> </html>
服务器:
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { res.send(new Date().toLocaleString()); }); app.listen(8888);
2. 长轮询(Long Polling)
长轮询是轮询的优化。它会等待上一次的响应完成后(返回需要的数据)或者请求超时后,再发起下一次的请求。
优点:
如果响应时间大于轮询的周期频率,会减少请求次数,实现节约流量(请求header/请求次数),提高CPU利用率的作用。
示例:
客户端:
<body> <div id="root"></div> <script> const xhr = new XMLHttpRequest(); // 自执行函数 (function longPolling() { xhr.open('GET', '/clock', true); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { // 等响应返回后再次发起请求 window.root.innerHTML = xhr.response; longPolling(); } } xhr.send(); })(); </script> </body>
服务器:
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { // 模拟响应时间较长的情况 setTimeout(function() { res.send(new Date().toLocaleString()); }, 3000); }); app.listen(8888);
3. iframe流
本质上是使用“长连接”,即请求发送后,定时返回响应,并不调用(res.end)断开连接。
在页面中嵌套一个隐藏的iframe,其src设置为一个长链接请求,服务器源源不断的向客户端推送数据。
示例:
客户端
<body> <div id="root"></div> <iframe src='/clock' style="display: none"></iframe> </body>
服务器
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { setInterval(function() { // 定时调用res.write/不调用res.end;即不断开连接 // 时间必须用双引号包裹,否则不会将其识别为字符串 res.write(` <script> parent.document.getElementById('root').innerHTML = "${new Date().toLocaleString()}" </script> `); }, 1000) }); app.listen(8888);
4. webSocket
WebSocket是H5提供的一种新的通信方式,可以使客户端和服务器保持持久连接。
WebSocket使用ws协议,和http协议是同级协议,都是应用层协议。而TCP协议是传输层协议。
WebSocket基于TCP协议,可以基于TCP实现webSocket服务器。并且复用http协议的握手通道。
优点:
1)全双工通信
2)请求头体积小
缺点:
1)不兼容
2)客户端和浏览器端用法不同
示例:
客户端
<body> 用户:<input id="username" /> 密码:<input id="password" type="password" /> <br/> <button onclick="login()">登录</button> <script> const ws = new WebSocket('ws://localhost:8888'); ws.onopen = function() { console.log('连接成功'); }; ws.onmessage = function(event) {// 监听响应事件 console.log('接受到服务器响应',event.data); }; // 登录函数 function login() { const username = document.getElementById('username').value; const password = document.getElementById('password').value; // 客户端发送登录信息 ws.send(JSON.stringify({type: 'login', username, password})); } </script> </body>
服务器
// 为静态资源文件起服务;http协议 const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { res.send(new Date().toLocaleString()); }); app.listen(8080); // ws协议;webSocket通信的服务器 const WebSocketServer = require('ws').Server; const { sign } = require('jsonwebtoken');// 使用JWT const server = new WebSocketServer({port: 8888}); server.on('connection', function(socket) { console.log('连接成功'); socket.on('message', function(message) { console.log('服务器接收到消息:',message); const {username, password, type} = JSON.parse(message); if(type === 'login') { // 同步获取token; sign的参数依次是data,secret,alg,callback(异步获取token) const token = sign({username, password}, 'lyra');// 默认Hmac SHA256 socket.send(JSON.stringify({type: 'logined', token})); } }) });
5. socket.io
socket.io是一个webSocket库,它包含浏览器端的js和服务器端的nodeJS, 目的是构建在不同浏览器和设备上的、通用的网络实时应用。
优点:
1. 易用性:它封装了客户端和服务端
2. 跨平台
3. 自适应: 可以根据浏览器自动从webSocket/长轮询/iframe流中选择
基础用法:
// 1. 全局广播 io.emit('message', content); // 2. 向除自己之外的所有人广播 socket.broadcast.emit('message', content); // 3.只广播到特定人(socket对应的客户端用户) socket.emit('message', content); // 或者 socket.send(content);