這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
這個問題?
這個問題一般會出現在面試題里面,然后回答一些諸如輪詢、WebSocket之類的答案,當然,實際開發中,也會遇到類似別人給你贊了,要通知給你的情況,這時服務端推送給Web前端(先局限在Web前端,畢竟其他端還有一些特殊方法)到底有多少種方法?它們到底是怎么實作的?
寫個Demo看看吧,這樣正好把主要(不清楚是否還有漏的)的方案都實作一遍,先看效果:

其中的代碼也上傳到GitHub了,在server-push( github.com/waiter/serv… )這里,
各種方案
從上面的截圖也已經可以看出,本文主要寫了5種方案,那么接下來也就一個一個簡單介紹一下吧,
另外,本文涉及的Demo,后端直接使用原生的Node.js開發,沒有使用Koa、Express之類的,也沒有使用額外的庫,類似socket.io,主要是想保持最精簡的狀態來呈現,前端也只是在最基礎的HTML上,引入了jQuery來方便做DOM操作,也引入了Bootstrap來快速實作統一的樣式,而未再引入類似Vue、React之類的框架,
還有,為了觸發服務端推送,這邊在前端頁面上加了個輸入框和按鈕,來將訊息發送給后端,后端會快取訊息,并觸發推送,后端大體代碼類似:
// 快取需要推送的資訊
const datas = [];
// 各種方案觸發推送時的回呼
const callbacks = {};
// 注冊介面回呼
server.on('request', (req, res) => {
const { pathname, query } = parse(req.url, true);
// 如果發現是前端觸發推送介面
if (pathname === '/api/push') {
if (query.info) {
// 快取推送資訊
datas.push(query.info);
const d = JSON.stringify([query.info]);
// 觸發所有推送回呼
Object.keys(callbacks).forEach(k => callbacks[k](d));
}
res.end('ok');
}
});
1. 輪詢(短輪詢)
這是最簡單直觀的方法,就是每隔一段時間發起一個請求到后端詢問是否有新資訊,至于為什么又叫短輪詢,其是相對于后續要說的長輪詢來對比的,
這樣前端只要設定一個setTimeout來定時請求就行:
// 快取前端已經獲取的最新id
let id = 0;
function poll() {
$.ajax({
url: '/api/polling',
data: { id },
}).done(res => {
id += res.length;
}).always(() => {
// 10s后再次請求
setTimeout(poll, 10000);
});
}
poll();
后端也是否簡單,根據前端給到的id,看看有沒有新訊息,有就回傳,沒有就回傳空
const id = parseInt(query.id || '0', 10) || 0;
res.writeHead(200, { 'Content-Type': 'application/json;' });
res.end(JSON.stringify(datas.slice(id)));
這個看起來其實時性與請求頻率成正相關,但是當請求頻率上來了,性能浪費也就越高,畢竟可能大部分請求都是無意義的,
2. 長輪詢
在翻找資料的時候,發現有些資料會直接把這個當作短輪詢,有點匪夷所思,這里的長輪詢相對前面的輪詢來說,算是一種優化,具體就是前端發起請求到后端,后端不直接回傳,而是等待有新資訊時再回傳,所以這樣發起的一個請求,可能需要很長的時間才能等到回傳,故而叫做長輪詢,
其前端代碼基本和短輪詢一致,只不過把請求的超時時間設定較長(比如1分鐘),然后無論請求成功或失敗,馬上再次發起請求即可,
相對來說,后端的寫法就要稍微改動一下
const id = parseInt(query.id || '0', 10) || 0;
const cbk = 'long-polling';
delete callbacks[cbk];
const data = https://www.cnblogs.com/smileZAZ/archive/2023/03/08/datas.slice(id);
res.writeHead(200, {'Content-Type': 'application/json' });
// 發起請求時,正好有新訊息就回傳
if (data.length) {
return res.end(JSON.stringify(data));
}
req.on('close', () => {
delete callbacks[cbk];
});
// 注冊新訊息回呼
callbacks[cbk] = (d) => {
res.end(d);
};
這樣,**相對于短輪詢,少了很多無意義的請求,而且訊息的實時性也非常好,**不過,當服務端有例外時,會導致長輪詢短時間內不斷發起請求,可能讓服務端承受更大的壓力,所以兩次長輪詢之間最好有一定間隔,或者例外檢測機制,
3. SSE(Server-sent events)
Traditionally, a web page has to send a request to the server to receive new data; that is, the page requests data from the server. With server-sent events, it's possible for a server to send new data to a web page at any time, by pushing messages to the web page. These incoming messages can be treated as Events + data inside the web page.
前面提到的輪詢、長輪詢都是一問一答式的,一次請求,無法推送多次訊息到前端,而SSE就厲害了,一次請求,N次推送,
其原理,或者說類比,個人認為可以理解為下載一個巨大的檔案,檔案的內容分塊傳給前端,每塊就是一次訊息推送,
聽起來很厲害,先看看后端代碼要怎么寫
const cbk = 'sse';
delete callbacks[cbk];
res.writeHead(200, {
// 這個是核心
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
});
// 把快取的資訊推送給前端
res.write(`data: ${JSON.stringify(datas)}\n\n`);
// 注冊新訊息回呼
callbacks[cbk] = (d) => {
res.write(`data: ${d}\n\n`);
};
req.on('close', () => {
delete callbacks[cbk];
});
后端代碼很簡單,核心在于Content-Type: text/event-stream,這要讓前端知道這是SSE,還有就是傳輸資訊的格式比較特別一點,詳細的可以看 MDN( developer.mozilla.org/en-US/docs/… )
而前端有專門的EventSource來接收,使用起來很方便
const es = new EventSource('/api/sse');
es.onmessage = (e) => {
try {
const c = JSON.parse(e.data);
} catch (err) {
console.log(err);
}
}
這樣就好了,如果你打開Chrome的開發者工具中的網路標簽,你就會發現Chrome對于SSE請求,有專門的展示標簽

另外,**SSE還支持自動重連!**服務器短時間例外,恢復之后,無需額外代碼,SSE就自動重連上了,不過,本人在實際作業中卻沒有碰到過SSE,也就在面試題中見過,
4. WebSocket
既然有了SSE,那還要WebSocket干啥啊?因為WebSocket可以一次連接,雙向推送,而SSE只能從服務端推送到前端,從這個角度來看,用WebSocket來單做服務端推送,有點大材小用了,
另外,初見WebSocket,可能會對其與Socket的聯系有點疑惑,Socket協議是與HTTP協議平級的,而WebSocket協議是基于HTTP協議的,不過兩者在使用層面上是十分相近的,
其前端使用寫法與SSE類似,十分簡單,只不過請求鏈接為ws://或者wss://開頭(相當于http://和https://)
const ws = new WebSocket('ws://localhost:3000/ws');
ws.onmessage = e => {
try {
const c = JSON.parse(e.data);
} catch (err) {
console.log(err);
}
};
而如果要用原生Node.js來寫WebSocket服務,就會麻煩一些了,一般情況都會使用類似socket.io之類的三方庫來降低實作成本,這邊也就在網上摘抄了一段代碼來簡單實作一下,詳細的可以看Github上的Demo代碼
server.on('upgrade', (req, socket) => {
const cbk = 'ws';
delete callbacks[cbk];
const acceptKey = req.headers['sec-websocket-key'];
const hash = generateAcceptValue(acceptKey);
const responseHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${hash}` ];
// 告知前端這是WebSocket協議
socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
// 發送資料
socket.write(constructReply(datas));
callbacks[cbk] = (d) => {
socket.write(constructReply(d));
}
socket.on('close', () => {
delete callbacks[cbk];
});
});
這個在Chrome瀏覽器中,也有專門的標簽頁展示

不過,它沒有像SSE一樣有自動重連,這塊需要自行實作,
一般網頁實時聊天之類需要雙向推送的,都會使用WebSocket來實作,
5. iFrame
這算是找資料的時候意外發現的,之前并不知道還有這樣的玩法,原理類似使用iFrame加載一個巨大的網頁,利用瀏覽器會一邊加載一邊決議執行回傳的HTML,通過分次回傳Script標簽來實作訊息推送,其實作類似SSE,不過看起來就比較==hack==,
前端代碼很簡單,只不過要注冊一個回呼給iframe使用
// 注冊給iframe使用的方法
window.change = function(data) {
};
$('body').append('<iframe src="https://www.cnblogs.com/api/iframe"></iframe>');
而后端也很簡單,有訊息的時候回傳script標簽即可
const cbk = 'iframe';
delete callbacks[cbk];
// 回傳快取資訊
res.write(`<script>window.parent.change(${JSON.stringify(datas)});</script>`);
callbacks[cbk] = (d) => {
res.write(`<script>window.parent.change(${d});</script>`);
};
req.on('close', () => {
delete callbacks[cbk];
});
相當奇淫巧技了,不過,似乎沒找到怎么判斷加載例外的情況,可能需要自行加心跳來實作了,
另外,很多文章在說使用iFrame方法時,會導致瀏覽器顯示未加載完,圖示一直轉的樣子,但是個人認為,圖示一直轉是因為頁面一直沒有onload,那么在頁面onload之后,再創建iFrame就應該沒有這個問題了,
總結一下
上面實作了5種推送的方案,弄了一個表格簡單對比一下
| 方案 | (準)實時 | 單次連接 | 自動重連 | 斷線檢測 | 雙向推送 | 無跨域 |
|---|---|---|---|---|---|---|
| 短輪詢 | ? | ? | ? | ? | ? | ? |
| 長輪詢 | ? | ? | ? | ? | ? | ? |
| SSE | ? | ? | ? | ? | ? | ? |
| WebSocket | ? | ? | ? | ? | ? | ? |
| iFrame | ? | ? | ? | ? | ? | ? |
本文轉載于:
https://juejin.cn/post/7113813187727720461
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/546247.html
標籤:其他
上一篇:CSS流動布局-頁面自適應
下一篇:java代碼審計-XSS

