WebSocket
function getUser() {
return new Promise((resolve) => {
let xhr = new XMLHttpRequest()
xhr.open('get', '/user', true)
xhr.onload = () => {
ul.innerHTML = ''
let users = JSON.parse(xhr.responseText)
users.forEach(item => {
let li = document.createElement('li')
li.innerHTML = item.name
ul.appendChild(li)
});
resolve()
}
xhr.send()
})
}
服务端
router.get('/user', ctx => {
//ctx.body = users
return new Promise((resolve)=>{
setTimeout(() => {
ctx.body = users
resolve()
}, 3000);
})
})
1. HTTP协议
HTTP
是一个数据传输协议,它主要是用来定义数据是如何进行传输的,以及数据包的格式:
- 客户端 -> 服务端(请求)
- 服务端 -> 客户端(响应)
请求/响应 模式
其采用 请求/响应 模式,即首先必须由客户端
发送请求,服务器
再对该请求进行响应(减少服务器开销)。
问题:服务端不能及时推送数据到客户端。
无状态 模式
HTTP 不保存(不持久化、不记忆)每次请求的客户端(减少服务器开销)。
问题:服务端无法得知当前客户端请求的内容是否已经处理过或是否已经验证过身份等
2. 轮询
客户端定时
向服务器发送Ajax请求,服务器接到请求后无论是否有响应的数据,都马上返回响应信息并关闭连接。
优点:实现简单。
缺点:浪费带宽和服务器资源,新数据响应会有延迟。
应用:小应用小场景
setInterval(() => {
getUser()
}, 1000);
简单理解:机器代替人工发请求
3. 长轮询
与简单轮询相似,只是在服务端在没有新的返回数据情况下不会立即响应,而会挂起,直到有数据或即将超时。
优点:实现也不复杂,同时相对轮询,节约带宽。
缺点:所以还是存在占用服务端资源的问题,虽然及时性比轮询要高,但是会在没有数据的时候在服务端挂起,所以会一直占用服务端资源,处理能力变少。
应用:一些早期的对及时性有一些要求的应用:web IM 聊天。
async function pollingReuqest() {
await getUser()
//当上一次请求完成后,再调用
setTimeout(() => {
pollingReuqest()
}, 1000);
}
4. EventSource
-
EventSource 是服务器推送的一个网络事件接口。一个EventSource实例会对HTTP服务开启一个持久化的连接,以text/event-stream 格式发送事件, 会一直保持开启直到被要求关闭。
-
一旦连接开启,来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个事件字段,触发的事件与事件字段的值相同。如果没有事件字段存在,则将触发通用事件。
-
与 WebSockets,不同的是,服务端推送是单向的。数据信息被单向从服务端到客户端分发. 当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类的,EventSource无疑是一个有效方案
-
使用 EventSource 类接口来完成请求
// /getData 为某个获取数据的url
let source = new EventSource("/getData")
- 服务端设置头信息
"Content-type": "text/event-stream"
- 返回数据格式
ctx.body = `event: ping\ndata: {"time": ${new Date()}}\n\n`
客户端
let source = new EventSource(`/users?count`);
source.addEventListener('ping', (e) => {
console.log(e.data);
let users = JSON.parse(e.data).users
}
服务端
ctx.set("Content-type", "text/event-stream")
return new Promise((resolve) => {
setTimeout(() => {
ctx.body = `event: ping\ndata: {"users": ${JSON.stringify(users)}}\n\n`
resolve();
}, 2000)
})
5. WebSocket
- 结合koa以及http模块
服务端代码
const http = require('http');
const Koa = require('koa');
const koaStaticCache = require('koa-static-cache');
const KoaRouter = require('koa-router');
const koaBody = require('koa-body');
const socketIo = require('socket.io');
// 创建一个 koa 实例
const app = new Koa();
app.use(koaStaticCache({
prefix: '/public',
dir: './public',
gzip: true,
dynamic: true
}));
const router = new KoaRouter();
router.get('/hello', async ctx => {
ctx.body = 'hello';
})
app.use(router.routes());
// app.listen(8888);
const server = http.createServer(app.callback());
server.listen(8888);
// koa 返回给我们的这个 app 不是 http.Server 对象
const socketServer = new socketIo.Server(server);
// 我们可以手动的维护当前所有链接的socket
// const clients = [
// {
// socket,
// uid: token.user.id
// },
// {
// socket,
// uid: token.user.id
// }
// ];
socketServer.on('connection', socket => {
console.log('有人链接了');
// console.log(socket);
// clients.push(socket);
socket.on('message', function (data) {
socket.emit('servermessage', {
id: socket.id,
time: getTime(),
data
});
// console.log('clients', clients.length);
// clients.forEach(client => {
// client.emit('servermessage', data);
// });
// 给其它已经链接的客户端(broadcast:不包含自己的)
socket.broadcast.emit('servermessage', {
// 从socket中的headers中获取原来登录的时候分配的token
id: socket.id,
data
});
})
})
function getTime() {
let date = new Date()
let Y = date.getFullYear()
let M = date.getMonth() + 1
M = M > 10 ? M : '0' + M
let D = date.getDate()
D = D > 10 ? D : '0' + D
let H = date.getHours()
H = H > 10 ? H : '0' + H
let m = date.getMinutes()
m = m > 10 ? m : '0' + m
let s = date.getSeconds()
s = s > 10 ? s : '0' + s
return Y + '-' + M + '-' + D + '\xa0' + H + ':' + m + ':' + s
}
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const formElement = document.querySelector('#form');
const inputElement = document.querySelector('#input');
const buttonElement = document.querySelector('button');
const messagesElement = document.querySelector('#messages');
formElement.onsubmit = function () {
return false;
}
const socket = io();
// 监听服务端消息
socket.on('servermessage', data => {
render(data);
});
buttonElement.onclick = function () {
const data = inputElement.value;
// 发送数据
// socket.emit('message', {
// targetId: 2,
// data
// });
socket.emit('message', data);
// render({
// data
// })
inputElement.value = '';
}
function render(message) {
let li = document.createElement('li');
li.innerHTML = `${message.id} ${message.time} 说:${message.data}`;
messagesElement.appendChild(li);
}
</script>
</body>