前言
餘為前端菜鳥,感姿勢水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣為引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。
本系列程式碼及文件均在 此處
常見實現方式
輪詢
最簡單的實現方式,由客戶端定時向服務端請求資料
簡單實現
// 設定定時器,每秒向伺服器請求資料
const xhr = new XMLHttpRequest()
setInterval(() => {
xhr.open('GET','/test')
xhr.onreadystatechange = () => {}
xhr.send()
}, 1000)
複製程式碼
評價
最簡單,但是請求次數太多,每次都要建立連線,對伺服器壓力也很大,大部分時間資料是沒有更新的,浪費頻寬。
長輪詢
服務端接收客戶端請求後暫時掛起,等待資料更新,有資料更新則響應,否則等到達到服務端設定的時間限制後再響應。客戶端接收到響應後會再發出請求,重新建立連線,如此往復。
簡單實現
- 前端請求
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}`)
}
複製程式碼
- node server
// 使用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'
}
})
複製程式碼
- 成果圖
評價
減少了請求次數,但服務端掛起依然是資源浪費。輪詢與長輪詢都是服務被動型,都是由客戶端發起請求。
具體程式碼見 github
長連線
SSE(Server-Sent Events)是H5新增的功能,允許服務端主動向客戶端推送資料。
簡單實現
- 客戶端
// 客戶端會在連線失敗後預設重連
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(); // 用於關閉連線
複製程式碼
- node server
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)
}
複製程式碼
返回的訊息格式應包含這幾個欄位
id: 1 // 事件id
event: test1 // 自定義事件,不設定則預設為message
data: {count: 1} // 資料
retry : 10000 // 重連時間
複製程式碼
- 成果圖
評價
與前兩者一樣基於HTTP協議,相比於長輪詢,不需要客戶端後續請求,只需要維持一個請求,後續服務端主動推送,且實現也比較簡單。
具體程式碼見 github
webSocket
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', () => { });
複製程式碼
- node server
// 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實現的是雙方你來我往的雙工通訊,更適用於即時通訊的場景。具體做這方面東西肯定會碰到一些坑的,這裡淺嘗輒止,以後有機會接觸再做深入。
雖發表於此,卻畢竟為一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。