余为前端菜鸟,感姿式水平匮乏,难观前端之大局。遂决定循前端知识之脉络,以兴趣为引,辅以几分坚持,望于己能解惑致知、于同道能助力一二,岂不美哉。前端
本系列代码及文档均在 此处node
最简单的实现方式,由客户端定时向服务端请求数据git
// 设置定时器,每秒向服务器请求数据
const xhr = new XMLHttpRequest()
setInterval(() => {
xhr.open('GET','/test')
xhr.onreadystatechange = () => {}
xhr.send()
}, 1000)
复制代码
最简单,可是请求次数太多,每次都要创建链接,对服务器压力也很大,大部分时间数据是没有更新的,浪费带宽。github
服务端接收客户端请求后暂时挂起,等待数据更新,有数据更新则响应,不然等到达到服务端设置的时间限制后再响应。客户端接收到响应后会再发出请求,从新创建链接,如此往复。web
ajax = () => {
const xhr = new XMLHttpRequest()
xhr.open('GET', '/test')
xhr.timeout = 10000
xhr.onreadystatechange = () => {
// 此时服务器已返回数据
if (xhr.readyState === 4) {
const content = document.getElementById("message")
content.innerHTML = `${content.innerHTML}\n${xhr.responseText}`
// 从新创建链接
ajax()
}
}
xhr.send()
}
window.onload = () => {
ajax()
}
复制代码
//
document.getElementById("sub").onclick = () => {
const xhr = new XMLHttpRequest()
const text = document.getElementById("text").value
xhr.open('POST', '/message')
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(`message=${text}`)
}
复制代码
// 使用EventEmitter进行事件监听
const EventEmitter = require('events').EventEmitter
const messageBus = new EventEmitter()
messageBus.setMaxListeners(100)
app.use(async (ctx) => {
if (ctx.request.url === '/test') {
const result = await new Promise((resolve, reject) => {
// 监听message,长轮询返回
messageBus.on('message', function (data) {
resolve(data)
})
})
ctx.body = result
}
// 接收到message,触发事件
if (ctx.request.url === '/message') {
messageBus.emit('message', ctx.request.body.message)
ctx.body = 'done'
}
})
复制代码
减小了请求次数,但服务端挂起依然是资源浪费。轮询与长轮询都是服务被动型,都是由客户端发起请求。ajax
具体代码见 github服务器
SSE(Server-Sent Events)是H5新增的功能,容许服务端主动向客户端推送数据。websocket
// 客户端会在链接失败后默认重连
const source = new EventSource('/sse')
// 默认为message,这里的test1为自定义
source.addEventListener('test1', (res) => {
console.log(res)
}, false)
source.onopen = () => {
console.log('open sse')
}
source.onerror = (err) => {
console.log(err)
}
// source.close(); // 用于关闭链接
复制代码
const Readable = require('stream').Readable
// 建立自定义流
function RR() {}
RR.prototype = Object.create(new Readable());
RR.prototype._read = function (data) {}
if (ctx.request.url === '/sse') {
// 设置响应头
ctx.set({
// 类型必须为event-stream
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
let stream = new RR()
let count = 1
stream.push(`event: test1\ndata: ${JSON.stringify({ count: count })}\n\n`)
// 返回的消息格式有要求,这里返回流是由于koa特殊
// 若是不是流会调用res.end(buffer),结束HTTP响应
ctx.body = stream
// 屡次主动响应,共用一个链接
const timer = setInterval(() => {
stream.push(`event: test1\ndata: ${JSON.stringify({ count: ++count })}\n\n`)
if (count > 5) {
clearInterval(timer)
}
ctx.body = stream
}, 2000)
}
复制代码
返回的消息格式应包含这几个字段app
id: 1 // 事件id
event: test1 // 自定义事件,不设置则默认为message
data: {count: 1} // 数据
retry : 10000 // 重连时间
复制代码
与前二者同样基于HTTP协议,相比于长轮询,不须要客户端后续请求,只须要维持一个请求,后续服务端主动推送,且实现也比较简单。koa
具体代码见 github
webSocket是有别于HTTP的一种新协议,诞生已有十年之久。webSocket握手阶段采用HTTP协议,没有同源限制,标识符为ws
// 原生写法
const ws = new WebSocket('ws://127.0.0.1:5001')
ws.readyState 0 正在链接 1 已链接 2 正在关闭 3 已关闭
ws.onopen = (evt) => {
console.log('opened')
ws.send('hello from client')
}
ws.onmessage = (evt) => {
console.log(`from server: ${evt.data}`)
ws.close()
}
// socket.io-client
// 服务端用的socket.io,客户端不用相应的client会有问题
const ws = io('ws://127.0.0.1:5001');
ws.on('connect', (evt) => {
console.log('opened')
ws.send('hello from client')
})
ws.on('message', (evt) => {
console.log(`from server: ${evt}`)
ws.close()
});
ws.on('disconnect', () => { });
复制代码
// with koa
const server = require('http').createServer(app.callback())
const io = require('socket.io')(server)
io.on('connection', (socket) => {
console.log('connected')
socket.on('message', (msg) => {
console.log(msg)
io.emit('message', 'hello from server');
});
});
server.listen('5001')
复制代码
使用起来很是简单,在单个TCP链接上实现客户端和服务端之间的全双工通讯,性能在几者中最好,后续想写聊天室玩的时候再来搞搞。
web即时通信其实要解决的一个是性能问题,一个是效率问题。性能上像长轮询和短轮询都是比较差的,效率我理解体如今实时性和主动性上。长链接和websocket均可以实现服务端主动推送,websocket实现的是双方你来我往的双工通讯,更适用于即时通信的场景。具体作这方面东西确定会碰到一些坑的,这里浅尝辄止,之后有机会接触再作深刻。
虽发表于此,却毕竟为一人之言,又是每日学有所得之笔记,内容未必详实,看官老爷们还望海涵。