本文由攜程前端開(kāi)發(fā)專(zhuān)家Chris Xia分享,關(guān)注新技術(shù)革新和研發(fā)效率提升。
1、引言
本文介紹了攜程機(jī)票前端基于Server-Sent Events(SSE)實(shí)現(xiàn)服務(wù)端推送的企業(yè)級(jí)全鏈路通用技術(shù)解決方案。文章深入探討了 SSE 技術(shù)在應(yīng)用過(guò)程中包括方案對(duì)比、技術(shù)選型、鏈路層優(yōu)化以及實(shí)際效果等多維度的技術(shù)細(xì)節(jié),為類(lèi)似使用場(chǎng)景提供普適性參考和借鑒。該方案設(shè)計(jì)目標(biāo)是實(shí)現(xiàn)通用性,適用于各種網(wǎng)絡(luò)架構(gòu)和業(yè)務(wù)場(chǎng)景。
2、技術(shù)背景
在如今互聯(lián)網(wǎng)應(yīng)用中,實(shí)時(shí)數(shù)據(jù)推送已成為很多業(yè)務(wù)場(chǎng)景的關(guān)鍵技術(shù)解決方案。攜程機(jī)票業(yè)務(wù)作為在線旅游行業(yè)的核心場(chǎng)景,面臨著航班數(shù)據(jù)實(shí)時(shí)性要求高、信息維度復(fù)雜等挑戰(zhàn)。
Server-Sent Events(SSE)技術(shù)作為一種基于 HTTP 長(zhǎng)連接的服務(wù)器推送方案,非常適用于機(jī)票業(yè)務(wù)"服務(wù)端主動(dòng)推送、客戶(hù)端實(shí)時(shí)展示"的需求特點(diǎn)。
相較于 WebSocket 等雙向通信協(xié)議,SSE 在實(shí)現(xiàn)簡(jiǎn)單性、協(xié)議輕量級(jí)和瀏覽器兼容性等方面具有顯著優(yōu)勢(shì),適合機(jī)票列表頁(yè)這類(lèi)以服務(wù)端數(shù)據(jù)為主導(dǎo)的業(yè)務(wù)場(chǎng)景。
3、認(rèn)識(shí)SSE
3.1 SSE 是什么?
Server-Sent Events(SSE)服務(wù)器發(fā)送事件,是一種基于 HTTP 長(zhǎng)連接,允許服務(wù)器單向?qū)崟r(shí)推送數(shù)據(jù)到客戶(hù)端的技術(shù)。
SSE 的工作原理非常簡(jiǎn)單直觀。客戶(hù)端通過(guò)與服務(wù)器建立一條持久化的 HTTP 連接,然后服務(wù)器使用該連接將數(shù)據(jù)以事件流(event stream)的形式發(fā)送給客戶(hù)端。這些事件流由多個(gè)事件(event)組成,每個(gè)事件包含一個(gè)標(biāo)識(shí)符、類(lèi)型和數(shù)據(jù)字段。客戶(hù)端通過(guò)監(jiān)聽(tīng)事件流來(lái)獲取最新的數(shù)據(jù),并在接收到事件后進(jìn)行處理。
關(guān)于SSE技術(shù)的詳細(xì)介紹可以閱讀《SSE技術(shù)詳解:一種全新的HTML5服務(wù)器推送事件技術(shù)》。
3.2 SSE 的使用場(chǎng)景
SSE 使用場(chǎng)景非常廣泛,大家熟知的 Chatgpt 對(duì)話的交互形式使用的就是 SSE 技術(shù)。
SSE 在服務(wù)器單向?qū)崟r(shí)推送數(shù)據(jù)的場(chǎng)景非常適用:
- 1)實(shí)時(shí)數(shù)據(jù)流:如股票市場(chǎng)更新、新聞推送、體育比分更新等;
- 2)實(shí)時(shí)通知:如社交媒體消息提醒、新訂單通知等;
- 3)儀表盤(pán)更新:如系統(tǒng)監(jiān)控、實(shí)時(shí)數(shù)據(jù)統(tǒng)計(jì)等。
關(guān)于SSE在如今熱門(mén)的AI大模型技術(shù)的中的應(yīng)用可以閱讀:《全民AI時(shí)代,大模型客戶(hù)端和服務(wù)端的實(shí)時(shí)通信到底用什么協(xié)議?》、《大模型時(shí)代多模型AI網(wǎng)關(guān)的架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)》。
4、先說(shuō)效果
機(jī)票前端首次在核心業(yè)務(wù)中(機(jī)票航班列表)使用 SSE 技術(shù),機(jī)票列表頁(yè)由原先客戶(hù)端串行請(qǐng)求獲取多批次航班數(shù)據(jù)變?yōu)橐淮握?qǐng)求由服務(wù)持續(xù)推送數(shù)據(jù)給客戶(hù)端。
在調(diào)研了公司內(nèi)外各種實(shí)現(xiàn)方案,最終聯(lián)合攜程框架、SRE、機(jī)票前后端團(tuán)隊(duì)共同實(shí)現(xiàn)了全公司通用的SSE技術(shù)解決方案(詳情見(jiàn)下文中的全鏈路支持部分)。
1)使用 SSE 前(如下圖):
- 1)客戶(hù)端需要發(fā)起兩次請(qǐng)求獲取完整航班數(shù)據(jù);
- 2)服務(wù)端采用預(yù)取優(yōu)化:在響應(yīng)第一次請(qǐng)求時(shí),提前獲取第二批數(shù)據(jù)并緩存至 Redis(降低客戶(hù)端第二次請(qǐng)求響應(yīng)的耗時(shí));
- 3)客戶(hù)端發(fā)起第二次請(qǐng)求時(shí),可直接獲取緩存數(shù)據(jù)。
這樣的流程和技術(shù)方案無(wú)疑會(huì)提升前后端的代碼復(fù)雜度,服務(wù)端需要額外增加一層緩存來(lái)提升響應(yīng)時(shí)間,客戶(hù)端無(wú)法感知服務(wù)到底有多少批次數(shù)據(jù),需要不斷問(wèn)詢(xún)。
2)使用 SSE 后(如下圖):
客戶(hù)端發(fā)送一次 SSE 請(qǐng)求,服務(wù)端實(shí)時(shí)推送數(shù)據(jù)到客戶(hù)端,服務(wù)間上下游同樣采用流式傳輸,實(shí)現(xiàn)客戶(hù)端到服務(wù)端全鏈路流式通信。
3)SSE 為前后端帶來(lái)的價(jià)值:
- 1)減少請(qǐng)求傳輸耗時(shí):無(wú)需請(qǐng)求多次,減少了多次請(qǐng)求的傳輸耗時(shí);
- 2)前后端代碼結(jié)構(gòu)優(yōu)化:代碼更簡(jiǎn)潔且易于理解,減少串行請(qǐng)求的回調(diào)監(jiān)聽(tīng)/嵌套;
- 3)服務(wù)邏輯優(yōu)化:列表數(shù)據(jù)移除了 redis 的發(fā)布訂閱流程,簡(jiǎn)化了代碼架構(gòu);
- 4)資源利用率提升:減少冗余請(qǐng)求(只有一批數(shù)據(jù)時(shí),客戶(hù)端不用再次請(qǐng)求問(wèn)詢(xún)服務(wù))。
4)SSE 對(duì)性能有提升嗎?
通過(guò)分析請(qǐng)求流程(建立鏈接 -> 發(fā)送請(qǐng)求 -> 響應(yīng)數(shù)據(jù)傳輸)和其原理,發(fā)現(xiàn) HTTP 1.1 和 2 支持鏈路復(fù)用,因此鏈接建立的次數(shù)本質(zhì)上沒(méi)有變化。在傳輸通道和數(shù)據(jù)壓縮方式保持不變的情況下,響應(yīng)數(shù)據(jù)傳輸?shù)暮臅r(shí)也不會(huì)有明顯變化。
SSE 的核心性能優(yōu)勢(shì)在于減少了請(qǐng)求發(fā)送的次數(shù),其性能增益取決于具體的使用場(chǎng)景:
a)當(dāng)服務(wù)端響應(yīng)耗時(shí)大于網(wǎng)絡(luò)傳輸耗時(shí),性能提升有限。
使用 SSE 與傳統(tǒng)串行請(qǐng)求的性能實(shí)驗(yàn)數(shù)據(jù)對(duì)比:
b)當(dāng)網(wǎng)絡(luò)傳輸耗時(shí)大于服務(wù)端處理耗時(shí),減少請(qǐng)求次數(shù)可以顯著降低整體延遲。
5、方案選型
目前市面上很多服務(wù)端推送的技術(shù)解決方案:SSE、輪詢(xún)/串行、Websocket 等,我們從易用性,資源開(kāi)銷(xiāo),使用場(chǎng)景等多維度對(duì)比了幾個(gè)使用較多的主流方案,最終選擇了 SSE。
5.1 服務(wù)端推送
簡(jiǎn)單幾行代碼實(shí)現(xiàn)服務(wù)端SSE推送。
SSE 的數(shù)據(jù)傳輸規(guī)范中有 4 個(gè)關(guān)鍵字段 event、data、id 和 retry,用于定義和傳輸事件數(shù)據(jù)。
具體是:
- 1)even:定義消息的事件類(lèi)型,客戶(hù)端可以根據(jù)事件類(lèi)型觸發(fā)不同的處理邏輯;
- 2)data:消息的主體內(nèi)容;
- 3)id:為消息設(shè)置一個(gè)唯一的 ID,用于客戶(hù)端斷線重連時(shí)標(biāo)識(shí)最后接收的消息;
- 4)retry:服務(wù)端指定客戶(hù)端在連接斷開(kāi)后重新連接的時(shí)間間隔(單位為毫秒)。
這些字段共同構(gòu)成了 SSE 消息的基本格式,每條消息以?xún)蓚€(gè)換行符 \n\n 結(jié)束,確保客戶(hù)端能夠正確解析和處理事件數(shù)據(jù)。
前端使用樣例:
// 創(chuàng)建 EventSource
const evtSource = new EventSource("接口地址");
// 監(jiān)聽(tīng)服務(wù)端推送的數(shù)據(jù)
evtSource.onmessage = function (event) {
console.log("接收到的消息:", event);
};
// 監(jiān)聽(tīng)連接建立
evtSource.onopen = function () {
console.log("連接已建立");
};
// 監(jiān)聽(tīng)報(bào)錯(cuò)
evtSource.onerror = function (err) {
console.error("發(fā)生異常:", err);
};
服務(wù)使用樣例(以 Nodejs 為例):
const http = require("http");
http
.createServer((req, res) => {
// 設(shè)置Response Header
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
// 不斷推送數(shù)據(jù)給客戶(hù)端
const pushData = setInterval(() => {
res.write(data);
}, 1000);
req.on("close", () => clearInterval(pushData));
})
.listen(3000);
5.2 內(nèi)部SSE實(shí)踐方案
調(diào)研發(fā)現(xiàn)公司內(nèi)部有兩套實(shí)踐方案:
- 1)自定義響應(yīng)式網(wǎng)關(guān):實(shí)現(xiàn)網(wǎng)關(guān)輪詢(xún)服務(wù)批量獲取數(shù)據(jù),從而實(shí)現(xiàn)流式傳輸。繞開(kāi)公司鏈路層,沒(méi)有通用性;
- 2)前端輪詢(xún)下沉BFF(服務(wù)):前端與BFF建立SSE通道,BFF不斷輪詢(xún)向上游批量獲取數(shù)據(jù)。輪詢(xún)位置發(fā)生變化,并未實(shí)現(xiàn)全鏈路的流式通信。
在攜程企業(yè)級(jí)網(wǎng)絡(luò)生態(tài)架構(gòu)下,從通用性和完整度分析對(duì)比了兩套方案,并沒(méi)有真正意義上從前到后打通整條鏈路。
僅僅只是簡(jiǎn)單接入SSE是遠(yuǎn)遠(yuǎn)不夠的,離不開(kāi)全鏈路(SSE技術(shù)選型,多層網(wǎng)絡(luò)架構(gòu)的適配,服務(wù)間的流式通信等等)的支持,所以最終決定聯(lián)合攜程框架、SRE、機(jī)票前后端團(tuán)隊(duì)共同來(lái)實(shí)現(xiàn)對(duì)SSE全鏈路的適配,真正意義上實(shí)現(xiàn)全公司通用的普適方案。
5.3 SSE技術(shù)選型
確定好整體技術(shù)方案后,我們?cè)趯?shí)際測(cè)試過(guò)程中發(fā)現(xiàn)了 2 個(gè) Web 原生 SSE 的局限性問(wèn)題。
具體是:
- 1)僅支持 Get 請(qǐng)求:對(duì)需要傳遞一些復(fù)雜請(qǐng)求體的場(chǎng)景不友好;
- 2)不支持自定義 http header:無(wú)法支持自定義 header 透?jìng)鳎b權(quán)等場(chǎng)景,目前市面大部分解決方案是使用 Cookie 來(lái)攜帶自定義參數(shù)。
針對(duì)上述問(wèn)題,調(diào)研發(fā)現(xiàn)微軟開(kāi)源的 SSE 網(wǎng)絡(luò)庫(kù) @microsoft/fetch-event-source(以下簡(jiǎn)稱(chēng) fes)能夠很好的解決。fes 是基于 Fetch 和 ReadableStream 來(lái)實(shí)現(xiàn)的 SSE 功能,旨在提供更加靈活便利的調(diào)用方式。
原生 SSE 和 fes 的對(duì)比:
fetch-event-source 詳解:fes 的核心原理是通過(guò) Fetch 發(fā)送請(qǐng)求,ReadableStream 讀取響應(yīng)流,在 JS 側(cè)實(shí)現(xiàn)字節(jié)流數(shù)據(jù)的解析。通過(guò)對(duì)比原生 SSE(chromium 內(nèi)核中 EventSource)和 fes 的代碼,發(fā)現(xiàn)整體流程與實(shí)現(xiàn)方案大致相同,關(guān)鍵區(qū)別在于流的解析,原生 SSE 在瀏覽器內(nèi)核由 C++實(shí)現(xiàn),fes 在 JS 側(cè)實(shí)現(xiàn)。
fes 的流解析:
- 1)核心方法:getBytes、getLines 和 getMessages;
- 2)getBytes:通過(guò) ReadableStream 讀取響應(yīng)字節(jié)流,獲取每個(gè)字節(jié)塊;
- 3)getLines:將 getBytes 獲取到的字節(jié)塊解析為 EventSource 行緩沖區(qū),處理這些字節(jié)塊并解析為行,然后調(diào)用 onLine 回調(diào)函數(shù)處理每一行;
- 4)getMessages:創(chuàng)建 EventSourceMessage 對(duì)象,將行緩沖區(qū)數(shù)據(jù)解析并進(jìn)行組裝,處理完成后回調(diào)給調(diào)用方。
export async function getBytes(stream: ReadableStream<Uint8Array>, onChunk: (arr: Uint8Array) => void) {
const reader = stream.getReader();
let result: ReadableStreamDefaultReadResult<Uint8Array>;
while (!(result = await reader.read()).done) {
onChunk(result.value);
}
}
export function getMessages(
onId: (id: string) => void,
onRetry: (retry: number) => void,
onMessage?: (msg: EventSourceMessage) => void
) {
let message = newMessage();
const decoder = new TextDecoder();
// return a function that can process each incoming line buffer:
return function onLine(line: Uint8Array, fieldLength: number) {
if (line.length === 0) {
// empty line denotes end of message. Trigger the callback and start a new message:
onMessage?.(message);
message = newMessage();
} else if (fieldLength > 0) { // exclude comments and lines with no values
// line is of format "<field>:<value>" or "<field>: <value>"
// [url=https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation]https://html.spec.whatwg.org/mul ... ream-interpretation[/url]
const field = decoder.decode(line.subarray(0, fieldLength));
const valueOffset = fieldLength + (line[fieldLength + 1] === ControlChars.Space ? 2 : 1);
const value = decoder.decode(line.subarray(valueOffset));
switch (field) {
case 'data':
// if this message already has data, append the new value to the old.
// otherwise, just set to the new value:
message.data = message.data
? message.data + '\n' + value
: value;
break;
case 'event':
message.event = value;
break;
case 'id':
onId(message.id = value);
break;
case 'retry':
const retry = parseInt(value, 10);
if (!isNaN(retry)) {
onRetry(message.retry = retry);
}
break;
}
}
}
}
export function getLines(onLine: (line: Uint8Array, fieldLength: number) => void) {
let buffer: Uint8Array | undefined;
let position: number; // current read position
let fieldLength: number; // length of the `field` portion of the line
let discardTrailingNewline = false;
return function onChunk(arr: Uint8Array) {
if (buffer === undefined) {
buffer = arr;
position = 0;
fieldLength = -1;
} else {
buffer = concat(buffer, arr);
}
const bufLength = buffer.length;
let lineStart = 0; // index where the current line starts
while (position < bufLength) {
if (discardTrailingNewline) {
if (buffer[position] === ControlChars.NewLine) {
lineStart = ++position; // skip to next char
}
discardTrailingNewline = false;
}
let lineEnd = -1; // index of the \r or \n char
for (; position < bufLength && lineEnd === -1; ++position) {
switch (buffer[position]) {
case ControlChars.Colon:
if (fieldLength === -1) { // first colon in line
fieldLength = position - lineStart;
}
break;
case ControlChars.CarriageReturn:
discardTrailingNewline = true;
case ControlChars.NewLine:
lineEnd = position;
break;
}
}
if (lineEnd === -1) {
break;
}
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
lineStart = position; // we're now on the next line
fieldLength = -1;
}
if (lineStart === bufLength) {
buffer = undefined; // we've finished reading it
} else if (lineStart !== 0) {
buffer = buffer.subarray(lineStart);
position -= lineStart;
}
}
}
6、全鏈路打通
企業(yè)級(jí)應(yīng)用時(shí),在非直連多層網(wǎng)絡(luò)架構(gòu)的環(huán)境下,應(yīng)用SSE不僅需要考慮前后端的使用,還需要考慮鏈路層、框架層、數(shù)據(jù)層等多環(huán)節(jié)的支持。通過(guò)不同團(tuán)隊(duì)(如框架、SRE、機(jī)票前端和后端團(tuán)隊(duì))的協(xié)作,開(kāi)發(fā)出一個(gè)在公司范圍內(nèi)通用的解決方案。
6.1 鏈路層
在攜程海外上云、多地多活服務(wù)架構(gòu)、多層網(wǎng)絡(luò)架構(gòu)的背景下,攜程框架及SRE團(tuán)隊(duì)提供了大力支持,完整打通了各鏈路層之間的流式傳輸。
多層網(wǎng)絡(luò)架構(gòu):
- 1)7層加速節(jié)點(diǎn)(akamai/aws):提供全球范圍內(nèi)的快速數(shù)據(jù)傳輸;
- 2)流量接入層(slb):確保高可用性和負(fù)載均衡;
- 3)中間轉(zhuǎn)發(fā)節(jié)點(diǎn)(蟲(chóng)洞):優(yōu)化跨Region數(shù)據(jù)傳輸路徑,減少延遲;
- 4)sidecar(envoy/nginx):容器流量管理,增強(qiáng)了應(yīng)用的可維護(hù)性和擴(kuò)展性。
對(duì)于絕大部分負(fù)載均衡,一般只保證完整報(bào)文的交付,并不保證報(bào)文的交付形式(流式/聚合),聚合場(chǎng)景下會(huì)導(dǎo)致"數(shù)據(jù)碎片"被聚合再交付,無(wú)法實(shí)現(xiàn)流式分批傳輸(如下圖所示)。
以Nginx為例:Nginx 會(huì)緩存代理服務(wù)器的響應(yīng)(聚合類(lèi)型),服務(wù)推送的數(shù)據(jù)被 Nginx 緩存到緩沖區(qū),導(dǎo)致客戶(hù)端沒(méi)有實(shí)時(shí)收到數(shù)據(jù),而是等到服務(wù)所有數(shù)據(jù)推送完后,客戶(hù)端才一次性收到了所有數(shù)據(jù)。
適配方案:禁用緩存功能,服務(wù)端響應(yīng)時(shí)除了設(shè)置 SSE 所必須的 Response Header 外,還需要添加非標(biāo) Header:X-Accel-Buffering: no,告知 Nginx 不緩存響應(yīng),確保數(shù)據(jù)實(shí)時(shí)發(fā)送到客戶(hù)端。
值得注意的是:在多層網(wǎng)絡(luò)架構(gòu)的環(huán)境下 X-Accel-Buffering: no Header 在各層網(wǎng)關(guān)之間轉(zhuǎn)發(fā)時(shí)會(huì)丟失,所以在多層網(wǎng)絡(luò)架構(gòu)下 Nginx 需要添加 proxy_pass_header X-Accel-Buffering,來(lái)確保整條鏈路上 Header 的傳遞。
6.2 框架層
前端框架團(tuán)隊(duì)基于fes實(shí)現(xiàn)SSE網(wǎng)絡(luò)請(qǐng)求,合并到公司基礎(chǔ)網(wǎng)絡(luò)框架,共享網(wǎng)絡(luò)優(yōu)化,監(jiān)控等基建能力,全公司通用。服務(wù)端基于Reactor + Dubbo Streaming實(shí)現(xiàn)服務(wù)間上下游全鏈路響應(yīng)式流式傳輸。
通過(guò)鏈路層的支持,從前端到服務(wù)端實(shí)現(xiàn)了統(tǒng)一的全鏈路流式傳輸通信,確保數(shù)據(jù)的高效傳輸和處理。
6.3 數(shù)據(jù)層
數(shù)據(jù)傳輸需注意代理服務(wù)器或 Web 容器(Nginx、Tomcat)對(duì)SSE MIME Type:text/event-stream的支持,未正確配置,服務(wù)端推送的數(shù)據(jù)不會(huì)經(jīng)過(guò)任何壓縮,傳輸數(shù)據(jù)大,導(dǎo)致客戶(hù)端響應(yīng)耗時(shí)增加。
適配方案:根據(jù)不同的服務(wù)器類(lèi)型進(jìn)行配置。
Nginx:
Tomcat:
7、全鏈路打通
本文介紹了 SSE 在攜程機(jī)票前端全鏈路企業(yè)級(jí)應(yīng)用實(shí)踐,解決了服務(wù)向前端實(shí)時(shí)推送數(shù)據(jù)的問(wèn)題。
通過(guò)合理的技術(shù)選型、流式數(shù)據(jù)解析和鏈路傳輸層優(yōu)化,從鏈路層,框架層,數(shù)據(jù)層全鏈路實(shí)現(xiàn)全公司通用的普適方案。降低了前后端代碼復(fù)雜度,提升了資源利用率。
隨著流式通信技術(shù)的不斷發(fā)展,SSE 將在更多場(chǎng)景中(覆蓋更多客戶(hù)端,支持更多網(wǎng)絡(luò)協(xié)議)發(fā)揮重要作用,為實(shí)時(shí)數(shù)據(jù)處理提供更高效的解決方案。
8、參考資料
[1] 新手入門(mén)貼:史上最全Web端即時(shí)通訊技術(shù)原理詳解
[2] Web端即時(shí)通訊技術(shù)盤(pán)點(diǎn):短輪詢(xún)、Comet、Websocket、SSE
[3] SSE技術(shù)詳解:一種全新的HTML5服務(wù)器推送事件技術(shù)
[4] 使用WebSocket和SSE技術(shù)實(shí)現(xiàn)Web端消息推送
[5] 詳解Web端通信方式的演進(jìn):從Ajax、JSONP 到 SSE、Websocket
[6] 網(wǎng)頁(yè)端IM通信技術(shù)快速入門(mén):短輪詢(xún)、長(zhǎng)輪詢(xún)、SSE、WebSocket
[7] 搞懂現(xiàn)代Web端即時(shí)通訊技術(shù)一文就夠:WebSocket、socket.io、SSE
[8] 全民AI時(shí)代,大模型客戶(hù)端和服務(wù)端的實(shí)時(shí)通信到底用什么協(xié)議?
[9] 大模型時(shí)代多模型AI網(wǎng)關(guān)的架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)
9、更多Web端即時(shí)通訊技術(shù)
一文讀懂前端技術(shù)演進(jìn):盤(pán)點(diǎn)Web前端20年的技術(shù)變遷史
Comet技術(shù)詳解:基于HTTP長(zhǎng)連接的Web端實(shí)時(shí)通信技術(shù)
新手快速入門(mén):WebSocket簡(jiǎn)明教程
理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性
WebSocket從入門(mén)到精通,半小時(shí)就夠!
LinkedIn的Web端即時(shí)通訊實(shí)踐:實(shí)現(xiàn)單機(jī)幾十萬(wàn)條長(zhǎng)連接
Web端即時(shí)通訊技術(shù)的發(fā)展與WebSocket、Socket.io的技術(shù)實(shí)踐
長(zhǎng)連接網(wǎng)關(guān)技術(shù)專(zhuān)題(四):愛(ài)奇藝WebSocket實(shí)時(shí)推送網(wǎng)關(guān)技術(shù)實(shí)踐
Web端即時(shí)通訊實(shí)踐干貨:如何讓你的WebSocket斷網(wǎng)重連更快速?
(本文已同步發(fā)布于:http://www.52im.net/thread-4832-1-1.html)