餘爲前端菜鳥,感姿式水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣爲引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。前端
本系列代碼及文檔均在 此處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實現的是雙方你來我往的雙工通訊,更適用於即時通信的場景。具體作這方面東西確定會碰到一些坑的,這裏淺嘗輒止,之後有機會接觸再作深刻。
雖發表於此,卻畢竟爲一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。