5种客户端与服务器之间双向通讯的方式总结(完整代码演示)
1、 polling 轮循
<!--轮循 polling -->
<div id="clock"></div>
<script>
let clock = document.getElementById('clock')
setInterval(() => {
let xhr = new XMLHttpRequest;
xhr.open('get','/clock',true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
clock.innerText = xhr.responseText
}
}
xhr.send()
}, 1000);
</script>
app.js: node模拟服务器,运行app.js, http://localhost:3000/访问index.html
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',function(req,res){
res.send(new Date().toLocaleString())
})
app.listen(3000)
可以看到的效果就是,每隔1秒发送一次请求,服务器返回更新后的信息。
2、long-polling 长轮循
长轮循:是对轮循的改良版,客户端发送请求给服务器之后,需要满足一些条件才返回新的数据,反之若没有新数据就一直等待。
当有新消息时才会返回给客户端,在某种程度上减少了网络带宽和CPU利用率的问题。
链接会一直保持,直到有数据更新或链接超时,此时服务器不能在发送数据。
代码实现:
index.html
<body>
<div id="clock"></div>
</body>
<script>
let clock = document.getElementById('clock')
function send(){
let xhr = new XMLHttpRequest;
xhr.open('get','/clock',true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
clock.innerText = xhr.responseText
send() // 服务器响应之后,在发送第二次请求
}
}
xhr.send()
}
send()
</script>
app.js: node模拟服务器,运行app.js, http://localhost:3000/访问index.html
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',function(req,res){
let $timer = setInterval(() => {
let date = new Date()
let seconds = date.getSeconds()
if(seconds % 5 == 0){ // 需要满足一些条件下,才会进行数据的返回
res.send(date.toLocaleString())
clearInterval($timer) // 清除定时器
}
}, 1000);
})
app.listen(3000)
可以看到的效果就是,服务器每隔5秒更新数据。然后在返回给客户端,结束一次请求响应,开始第二次的请求响应,而实际应用场景中,服务器的响应时候,会更长一些。
3、 iframe 流
在html 页面嵌入一个隐藏的iframe,将这个iframe的src 属性设为对一个长链接的请求,服务器端就能源源不断的向客户端推送数据
代码实现:
index.html
<style>
div{
height: 100px;
width:230px;
border: 1px solid slateblue;
line-height: 100px;
text-align: center;
}
</style>
<body>
<!-- iframe 流 -->
<div id="clock"></div>
<iframe src="/clock" frameborder="0"></iframe>
</body>
<script>
let clock = document.getElementById('clock')
function setTime(st) {
clock.innerText = st
}
</script>
app.js :node模拟服务器,运行app.js, http://localhost:3000/访问index.html
let express = require('express')
let app = express()
app.use(express.static(__dirname))
app.get('/clock',function(req,res){
setInterval(() => {
res.write(` // 此处 注意不能用上述案例中的send方法,send方法会默认执行end结束,
<script>
parent.setTime('${new Date().toLocaleString()}')
</script>
`)
}, 1000);
})
app.listen(3000)
可以看到的效果就是,服务器每隔1秒更新数据。并主动推送给客户端,但是同样存在问题,就是页签上的icon一直处于loading状态,表示响应一直未结束。用户体验并不是很好。
4、 EventSource 流
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。
此时客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
H5规范中提供了服务端事件EventSource,浏览器创建一个EventSource连接后,便可收到服务端发送的数据,这些数据需要遵守一定的格式,直到服务端或者客户端关闭该流,所以eventSource也叫做SSE(server-send-event)。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。
目前除了 IE/Edge,其他浏览器都支持,实现方式对客户端开发人员而言非常简单,只需在浏览器中监听对应的事件即可。
另外对于服务器,SSE使用的也是HTTP传输协议,这意味着我们不需要一个特殊的协议或者额外的实现就可以使用。
其实说白了SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
应用场景:在股票行情、新闻推送的这种只需要服务器发送消息给客户端场景中,显然使用SSE更加合适。
EventSource的实现同样分为两部分:
浏览器端:
在浏览器端创建一个EventSource实例,向服务器发起连接
open:连接一旦建立,就会触发open事件,可以在onopen属性定义回调函数
message:客户端收到服务器发来的数据,就会触发message事件,可以在onmessage属性定义回调函数。
error:如果发生通信错误(如连接中断,服务器返回数据失败),就会触发error事件,可以在onerror属性定义回调函数。
close:用于关闭 SSE 连接。source.close();
自定义事件:EventSource规范允许服务器端执行自定义事件,客户端监听该事件即可,需要使用addEventListener
index.html
<style>
div{
border: 1px solid #ce4;
width: 300px;
height: 40px;
padding: 20px;
margin-bottom: 20px;
}
p{
color: #888;
font-size: 14px;
}
</style>
<body>
<p>默认事件message:</p>
<div id="clock"></div>
<p>自定义事件 yya:</p>
<div id="yya"></div>
</body>
<script>
let sse = new EventSource('/clock')
let clock = document.getElementById('clock')
let yya = document.getElementById('yya')
// 监听连接刚打开时被调用
sse.onopen = function () {
console.log('open');
}
// 监听服务器发过来的信息
sse.onmessage = function (event) {
clock.innerText = event.data
}
// 监听链接请求失败 关闭流
sse.onerror = function (event) {
console.log('error');
sse.close();
}
// 监听自定义事件, 不能通过on的方式的去绑定
sse.addEventListener('yya', function (event) {
yya.innerText = event.data
}, false);
</script>
服务器端:
事件流的对应MIME格式为text/event-stream。
服务器向浏览器发送的 SSE 数据,必须是 UTF-8 编码的文本
服务端返回数据需要特殊的格式,分为四种消息类型,且消息的每个字段使用"\n"来做分割,
Event: 事件类型,支持自定义事件
Data: 发送的数据内容,如果数据很长,可以分成多行,用\n结尾,最后一行用\n\n结尾。
ID: 每一条事件流的ID,相当于每一条数据的编号,
Retry:指定浏览器重新发起连接的时间间隔。在自动重连过程中,之前收到的最后一个ID会被发送到服务端。
app.js: node模拟服务器,运行app.js, http://localhost:3000/访问index.html
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)
实现效果:可以看到已经很好的解决了在iframe流中遗留的问题,也就是页签一直loading的现象。
5、 websocket
WebSocket 是H5下一种新的协议,它诞生于2008年,2011年成为国际标准,现在所有浏览器都已支持。
它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽,并达到实时通讯的目的,最大特点就是:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。
优势及特点:
在客户端和服务器之间保有一个持有的连接,两边可以随时给对方发送数据,有很强的实时性;
属于应用层协议,基于TCP传输协议,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器;
可以发送文本,也可以支持二进制数据的传输;
数据格式比较轻量,性能开销小,通信高效;
没有同源限制,客户端可以与任意服务器通信;
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL;
WebSocket所涉及的内容远不止于此,这里只是抛砖引玉,带大家入门,知道有这么个东西,可以干一件什么样的事儿,要想完全掌握还需要下一番功夫嘞,那么就简单的用代码实现一下双工通讯,并使用序号标识出执行顺序:
index.html
<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>
app.js:node模拟服务器,运行app.js, http://localhost:3000/访问index.html
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.服务端说:你也好')
})
})
因为我在执行的时候,突然有一个疑问,就是客户端连接服务器 和 服务器监听客户端的连接,那个方法先执行,于是做了一个实验,在打印的时候,分别打印出了对应的时间戳,打印结果显示大部分情况是2先执行,个别时候是同时执行,我试了很多次,没有出现1比2先的情况。
期待有懂的小伙伴评论区指点一二~~~
以上就是我今天要分享的全部内容!
学习更多技能
请点击下方公众号