Comet,SSE,WebSocket前後端的實現
Comet(服務器推送)的兩種方式
短輪詢
頁面定時向服務器發送請求, 步驟為:建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接
//前端js var xhr = new XMLHttpRequest(); setInterval(()=>{ xhr.onreadystatechange = function () { if (xhr.readyState == 4) { let result = xhr.responseText console.log(result); } } xhr.open('get', '/front/test'); xhr.send(null); },3000) } //後端 node const Koa = require('koa'); const app = new Koa(); const router = require('koa-router')() const koaBody = require('koa-body'); router.get('/front/test', koaBody(), ctx=>{ // ctx.set('Connection', 'close'); 不設置response.header,默認長連接,否則為短鏈接 ctx.body = { title: 'chuchur'} }); app.use(router.routes()); app.listen(7005, () => { console.log('server run in http://localhost:7005'); });
長輪詢
長輪詢的方式是,頁面向服務器發起一個請求,服務器一直保持tcp連接打開,知道有數據可發送。發送完數據後,頁面關閉該連接,隨即又發起一個新的服務器請求,在這一過程中循環,步驟為:建立連接——數據傳輸...(保存連接)...數據傳輸——關閉連接
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { let result = xhr.responseText console.log(result); xhr.open('get', '/front/test'); //在獲得數據後重新向服務器發起請求 xhr.send(null); } } xhr.open('get', '/front/test'); xhr.send(null);
短輪詢和長輪詢的區別是:短輪詢中服務器對請求立即響應,而長輪詢中服務器等待新的數據到來才響應,因此實現了服務器向頁面推送實時,並減少了頁面的請求次數。
http流
//前端 var xhr = new XMLHttpRequest(); var received = 0; //最新消息在響應消息的位置 xhr.onreadystatechange = function () { if (xhr.readyState == 3) { let result = xhr.responseText.substring(received); console.log(result); received += result.length; } else if (xhr.readyState == 4) { console.log("完成消息推送"); } } xhr.open('get', '/front/test'); xhr.send(null); //node router.get('/front/test', koaBody(), ctx=>{ ctx.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); ctx.body = { title: 'chuchur'} });
http流不同於上述兩種輪詢,因為它在頁面整個生命周期內只使用一個HTTP連接,具體使用方法即頁面向瀏覽器發送一個請求,而服務器保持tcp連接打開,然後不斷向瀏覽器發送數據。
SSE eventSource
eventSource是用來解決web上服務器端向客戶端推送消息的問題的。不同於ajax輪詢的復雜和websocket的資源占用過大,eventSource(sse)是一個輕量級的,易使用的消息推送API ,大多數瀏覽器實現了SSE(Server-Sent Events,服務器發送事件) API,SSE支持短輪詢、長輪詢和HTTP流
前端實現
//生成EventSource對象,url必須同源
var evtSource = new EventSource("/front/test");
//收到服務器發生的事件時觸發,如果連接斷開,還會自動重新連接
evtSource.onmessage = function (e) {
console.log(e.data);
}
//成功與服務器發生連接時觸發
evtSource.onopen = function () {
console.log("Server open")
}
//出現錯誤時觸發
evtSource.onerror = function (e) {
console.log("Error")
}
//自定義事件
evtSource.addEventListener("test", function (e) {
console.log(e.data);
}, false)
服務端實現
//node
router.get('/front/test', koaBody(), ctx=>{
ctx.set({
'Content-Type': 'text/event-stream', //事件流的對應MIME格式為text/event-stream
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
ctx.body =
'id:11\n'+
'retry: 100\n'+
'event:test\n'+
'data: hello word\n\n'
});
服務端返回數據需要特殊的格式,它分為四種消息類型:event, data, id, retry
- event指定自定義消息的名稱
- data指定具體的消息體,可以是對象或者字符串
- id為當前消息的標識符,可以不設置
- retry設置當前http連接失敗後,重新連接的間隔。EventSource規範規定,客戶端在http連接失敗後默認進行重新連接,重連間隔為3s,通過設置retry字段可指定重連間隔;
每個字段都有名稱,緊接著有個”:“。當出現一個沒有名稱的字段而只有”:“時,這就會被服務端理解為”註釋“,並不會被發送至瀏覽器端,如: commision
WebSocket 全雙工通訊
WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以後,客戶端和服務器端就可以通過 TCP 連接直接交換數據。
前端實現
let socket = new WebSocket('ws://localhost:7005/ws/test')
// 建立連接
socket.onopen = () => {
console.log('connection')
//發送消息,告訴服務器,我上線了.
socket.send(JSON.stringify({ name: 'chuchur' + Date.now(), msg: '我來了' }))
}
//接收消息
socket.onmessage = (evt) => {
let data = JSON.parse(evt.data);
console.log(data)
}
//socket 斷開
socket.onclose = () => {
console.log('close')
}
//socket 發生錯誤
socket.onerror = () => {
console.log('error')
}
後端的實現
//引入相關ws庫
....
const WebSocketServer = require('ws').Server;
//此處定義變量
const server = app.listen(7005, () => {
console.log('server run in http://localhost:7005');
});
//拿到ws對象
const wss = new WebSocketServer({ server });
//鏈接成功
wss.on('connection', ws => {
console.log('有人來了') //當客戶端連接之後
//接收消息
ws.on('message', data => {
data = JSON.parse(data)
console.log(data)
if (data.name) {
//廣播,告訴大家,有人來了
wss.broadcast(JSON.stringify({ msg: data.name + '來了,大家歡迎' }))
//記錄ID,可以實現客戶端一對一,或一對多通訊,ID為唯一值
ws.id = data.name
}
//實現一對一通話
if (data.toID) {
wss.clients.forEach(client => {
if (data.toID == client.id) {
client.send(JSON.stringify({ msg: ws.name + '對你說:' + data.msg }))
return;
}
})
}
})
//發送消息
ws.send(JSON.stringify({ msg: '你來了, 獎勵一顆糖' }));
})
//廣播
wss.broadcast = data => {
wss.clients.forEach(client => {
client.send(data);
});
};
setInterval(() => {
console.log('當前在線人數:' + wss.clients.size)
}, 1000);
ws轉發配置
//webpack 開發配置
devServer: {
...
proxy: {
'/ws': {
target: 'http://120.0.0.1:7005', //轉發到node
changeOrigin: true,
}
}
}
//nginx 部署
server {
listen 80;
server_name localhost;
# 處理WebSocket連接:
location ^~ /ws/ {
proxy_pass http://127.0.0.1:7005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 其他所有請求:
location / {
proxy_pass http://127.0.0.1:7005;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
關於更多的ws相關配置,可以參考 WS
開發過程中需要自行做轉發
總結
- ajax短輪詢:省事,最簡單,不論服務端是否返回數據,埋頭苦幹,任勞任怨
- 長輪詢:也是埋頭,只不過是,拿到數據才做出反應. 服務器有個阻塞的過程.
- SSE:可以接收服務端推送.接收http流,雙向可控
- Socket:全雙工通訊,功能強大, 耗資源
各有優缺點, 主要是看什麽場景用什麽.
Comet,SSE,WebSocket前後端的實現