前言
由于http半双工的特性, 同一时刻只允客户端要先向服务器发送请求,然后服务器进行响应, 服务器不能主动推送数据给服务器。 为了解决这个问题, 通常情况下我们可以采取以下的几种方式:
1.轮询
就是客户端过一段时间就调用后台接口发出请求 , 然后服务器进行响应, 造成了服务器推送的假象。 缺点很明显:耗费流量耗费cpu的利用率。 具体的实现代码是: server:
let express = require("express");
let app = express();
const cors = require("cors");
app.use(cors());
app.get("/", (req, res) => {
res.end(new Date().toLocaleTimeString());
});
app.listen(8080, function () {
console.log("reaedy");
});
客户端:
<script>
setInterval(function () {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8080', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log( xhr.responseText)
}
}
xhr.send();
}, 1000);
</script>
2.长轮询
长轮询是对轮询的一个改进, 具体的流程是:
- 客户端发送请求
- 服务端接收到请求之后如果没有消息就不返回,连接不断开
- 当服务端有消息返回的时候, 服务端断开,同时浏览器发出一个新的请求。 可以看出相对于短轮询来说, 减少了请求次数 , 也就减少了资源的浪费, 但是一个http header还是很大 , 无可避免的还是会造成浪费。 实现代码是: 服务端:
let express = require("express");
let app = express();
const cors = require("cors");
app.use(cors());
app.get("/", (req, res) => {
setTimeout(() => {
res.end(new Date().toLocaleTimeString());
}, 10000);
});
app.listen(8080, function () {
console.log("reaedy");
});
客户端:
(function getRes () {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8080', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log( xhr.responseText)
getRes()
}
}
xhr.send();
})()
3.server-sent-events
上面两种方式都是一问一答式的 , sse是一次请求, 多次推送。一个客户端去从服务器端订阅一条流
,之后服务端可以发送消息给客户端直到服务端或者客户端关闭该“流”。
上代码:
sse是支持重连的 , 短时间断开的话 , 会重新连接,比如我下面代码改成count++==10的时候res.end(),然后再浏览器的network上又会看到重新连接了。
服务端:
一定要注意res.write 的格式一定要加上data: ,最后以两个换行结束 , 不然会报错。
支持自定义其他事件, 把event对应的name改一下就好了res.write(event:test\nid:${++count}\ndata:hello${count}\n\n
);
客户端监听事件是 :
eventSource.addEventListener('test', function (res){
console.log(1111, res)
})
let express = require("express");
let app = express();
const cors = require("cors");
app.use(cors());
app.get("/hello", (req, res) => {
res.header("Content-Type", "text/event-stream");
let count = 0;
setInterval(() => {
res.write(`event:message\nid:${count++}\ndata:hello${Date.now()}\n\n`);
// res.write(`data:hello${Date.now()}\n\n`);
}, 1000);
});
app.listen(8080, function () {
console.log("reaedy");
});
客户端: 使用h5自带的eventsource
const eventSource = new EventSource('http://localhost:8080/hello')
eventSource.onmessage = function (res) {
console.log(res)
}
eventSource.onerror = function (){}
4.iframe 实现服务器推送
iframe的src会设置长连接,具体步骤如下:
- iframe设置display:none
- src设置为请求地址
- 通过addEventListener('message')进行监听。 优点是浏览器的兼容性比较好。 缺点是:
- 由于连接没有断开 , 所以浏览器标签页的 loading icon会一直的旋转
- 服务器维护长连接需要一个资源的浪费 注意点: parent.postMessage是因为出现了跨域 ,第一个参数是value值, 记得加上引号,因为不然就是一个变量,第二个参数是客户端的url,这样做可以解决跨域问题。 服务端:
let express = require("express");
let app = express();
const cors = require("cors");
app.use(cors());
let count = 0;
app.get("/hello", (req, res) => {
setInterval(() => {
res.write(`
<script>
parent.postMessage(
' hello${count++}',
"http://127.0.0.1:7000/"
);
// parent.getElementById("clock").innerHTML(hello${count++})
</script>
`);
}, 1000);
});
app.listen(8080, function () {
console.log("reaedy");
});
客户端
<body>
<iframe src="http://localhost:8080/hello" frameborder="0" style="display:none"></iframe>
<script>
window.addEventListener("message", function( event ) {
console.log(12313123,event)
});
</script>
5.websocket
WebSocket是一种全新的协议,随着HTML5草案的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道。 WebSocket协议是借用HTTP协议的101 switchprotocol(服务器根据客户端的指定,将协议转换成为 Upgrade首部所列的协议)来达到协议转换的,从HTTP协议切换成WebSocket通信协议。
如何建立连接
- 首先客户端发出了一个ws的请求 ,可以看出采用了http的报文格式
GET ws://localhost:8080/ws HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 8cQjKCYrFciflQ2VcsMZjA==
2.服务器相应 , status切换为101表示协议切换。
服务端使用的是ws这个库
let WebSocketServer = require("ws").Server;
let wsServer = new WebSocketServer({ port: 8888 });
wsServer.on("connection", function (socket) {
console.log("连接成功");
socket.on("message", function (message) {
console.log("接收到客户端消息:" + message);
socket.send("服务器回应:" + message);
});
});
客户端使用了websocket
let ws = new WebSocket("ws://localhost:8888");
ws.onopen = function () {
console.log("客户端连接成功");
ws.send("hello");
};
ws.onmessage = function (event) {
console.log("收到服务器的响应 " + event.data);
};
sse跟websocket的区别:
- websocket是一种全双工协议 , 但是sse是半双工的
- sse使用的是http协议 , websocket是基于websocket协议
- sse轻量级的
- sse默认支持断线重连 , websocket需要自己去实现
- sse一般用来传送文本,mime类型是text/event-stream
socket.io
详见文档
socket.io 是一个websocket库 , 包含了客户端和服务端 既然websocket相对于前面说的那几种已经可以实现相互通信了 , 为什么我们还要使用socket.io呢? 这是因为socket.io有下面几种优点 :
- 易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。
- 跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。
- 自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。
服务端:
var express = require("express");
var path = require("path");
var app = express();
const { Server } = require("socket.io");
app.get("/", function (req, res) {
res.sendFile(path.resolve("index.html"));
});
var server = require("http").createServer(app);
var io = new Server(server, {
cors: true,
origins: ["http://localhost:8002/"],
}); //可以解决跨域问题
io.on("connection", function (socket) {
console.log("connect1111");
socket.send("sever send:");
io.on("message", function (msg) {
console.log(msg);
});
});
server.listen(8001);
客户端
import { io } from "socket.io-client";
const socket = io("http://localhost:8001");
socket.on("connect", function () {
console.log("connect");
socket.send("hello");
socket.on("message", function (msg) {
console.log(1111, msg);
});
});