客户端与服务端实时通讯
背景
在某些项目中,某些数据需要展示最新的,实时的,这时候就需要和服务端进行长时间通讯
方案
对于数据实时获取,我们一般会有4种方案:
1.短轮询:使用浏览器的定时器发起http请求,每隔一段时间就请求一次
2.长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
3.websocket:客户端与服务端建立websocket连接,搭建双工(双向)通道
4.SSE(Server-Sent Events):基于HTTP的html5新特性,服务器推送,属于半双工通信模型
轮询
轮询是指客户端定时向服务器发送ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
轮询分为短轮询和长轮询
短轮询
实现原理: 短轮询指客户端每间隔一段时间向服务端发起请求,保持数据的同步。
优点: 可实现基础(指间隔时间较短)的数据更新。
缺点: 这种方法也只是尽量的模拟即时传输,但并非真正意义上的即时通讯,坏处是间隔设置的太长用户体验不好,设置间隔太短后端服务会进行大量的无效查询并且数据没有及时返回前端展示给用户。
长轮询
实现原理: 客户端发送请求后,如果没有数据返回,服务端会将请求放入队列一直连接处理其他请求,直到有数据才返回给客户端,然后客户端再次发起请求,以此轮询。在 HTTP1.0 中客户端可以设置请求头 Connection:keep-alive,服务端收到该请求头之后知道这是一个长连接,在响应报文头中也添加 Connection:keep-alive。客户端收到之后表示长连接建立完成,可以继续发送其他的请求。在 HTTP1.1 中默认使用了 Connection:keep-alive 长连接。
优点: 减少了大量无效的查询,保证每次请求都有数据返回,不会一直占用线程。
缺点: 如果新数据频繁,会进行大量的连接建立和关闭,对服务器的处理能力要求较高。服务器一直保持连接会消耗资源,需要同时维护多个线程,服务器所能承载的 TCP 连接数是有上限的,这种轮询很容易把连接数顶满。每次通讯都需要客户端发起,服务端不能主动推送。
websocket
实现原理: Websocket 实现了客户端与服务端的双向(双工)通信,只需要连接一次,就可以相互传输数据,很适合实时通讯、数据实时更新等场景。
优点:
1.双向通讯:客户端服务端双方都可以主动发起通讯
2.没有同源限制:相对于tcp请求websocket客户端可以与任意服务端通讯,不存在跨域的情况
3.数据量轻:只需要第一次连接时携带请求头,后面的数据通讯都不需要携带请求头
4.传输效率高,因为只需要一次连接
缺点:
1.长连接需要后端处理业务的代码更稳定,推送消息相对复杂
2.长连接对网络要求比较大,需要处理好重连接
3.兼容性差,Websocket只支持IE10及以上
使用
1.客户端代码
<script>
let socket = new WebSocket('ws://localhost:8888')
socket.onopen = function () {
console.log('1. 客户端连接上了服务器',new Date().getTime());
socket.send('3. 你好')
}
socket.onmessage = function (e) {
console.log('6',e.data);
}
</script>
2.服务端代码
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.listen(3000)
let WebSocket = require('ws')
let wss = new WebSocket.Server({port:8888})
wss.on('connection',function(ws){
console.log('2.服务器监听到了客户端的连接',new Date().getTime());
ws.on('message',function(data){
console.log('4.客户端发来的消息',data);
ws.send('5.服务端说:你也好')
})
})
SSE(EventSource)
实现原理: 客户端首先向服务器发送一个 HTTP 请求,然后服务器设置响应头并保持这个连接打开,并周期性地通过这个连接向客户端发送数据。每个数据块都是一个独立的消息。
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
优点:
1.简单易用:只需要在客户端创建一个EventSource对象,指定服务器端的URL,即可进行监听并展示事件。
2.网络带宽节省:EventSource采用长连接的方式进行数据传输,相比于普通的轮询方式,能够节省大量的网络带宽。
3.跨域支持:允许在跨域环境下进行通信,通过适当的响应头授权来自不同域的客户端连接。
缺点:
1.单项通信:只支持服务器向客户端的单向通信,无法实现客户端向服务器的实时交互。
2.不能重连:如果网络连接不稳定,或者服务器端关闭EventStream连接,客户端需要重新连接才能继续监听事件。
3.不支持二进制数据传输:只能传输文本数据,不能传输二进制数据,这在某些场景下可能存在一定的局限性。
使用
1.客户端
var eventSource = new EventSource("/clock");
eventSource.onmessage = function(event) {
console.log("Received event: " + event.data);
};
2.服务端
let express = require('express')
let app = express()
app.use(express.static(__dirname))
let counter = 0
app.get('/clock',function(req,res){
res.header('Content-Type','text/event-stream')
let $timer = setInterval(() => {
// 第一种写法
res.write(`id:${counter++}\nevent:message\ndata:${new Date().toLocaleString()}\n\n`)
// 另一种写法
res.write(`event:yya\n`) // 触发 自定义事件
res.write(`data:${counter}\n\n`)
}, 1000 );
res.on('close',function(){
counter = 0
clearInterval($timer)
})
})
app.listen(3000)
chatGPT就是使用的这种方式
响应头
响应的数据