一文探究web實時通信方案并深入websocket原理與應(yīng)用

背景

在最近的項目中爱态,有一個數(shù)據(jù)展示的需求衬潦,要求是實時展示各組顏色與倒計時。在技術(shù)層面就是延時要控制到非常低隆圆。

對于實時類信息獲取漱挚,我們一般會有4種方案:

  • 輪詢,瀏覽器的定時器發(fā)起http請求
  • 長輪詢(Comet)渺氧,http1.1支持的由瀏覽器發(fā)起的長輪詢
  • websocket旨涝,瀏覽器與后端服務(wù)器建立websocket連接,雙工(雙向)通信
  • SSE(Server-Sent Events)侣背,基于HTTP的html5新特性颊糜,服務(wù)器推送哩治,半雙工通信模型

ps:http2.0中有一個服務(wù)器推送不是實時需求的方案秃踩,這個特性是服務(wù)端根據(jù)客戶端的請求衬鱼,提前返回多個響應(yīng),推送額外的資源給客戶端憔杨。如果一個請求是由你的主頁發(fā)送的鸟赫,服務(wù)器可能會響應(yīng)主頁內(nèi)容、logo以及樣式表消别,因為他知道客戶端會用到這些東西抛蚤。這樣不但減輕了數(shù)據(jù)傳送冗余步驟,也加快了頁面響應(yīng)的速度寻狂,提高了用戶體驗岁经。

基于 Flash的socket實現(xiàn)逐漸淘汰,不在考慮范圍內(nèi)蛇券。

以下文字版本demo是參考知乎用戶@Ovear的回答缀壤,推薦大家看下原文,順便看下該問題的其他回答:https://www.zhihu.com/question/20215561

輪詢

輪詢是指客戶端定時向服務(wù)器發(fā)送ajax請求纠亚,服務(wù)器接到請求后馬上返回響應(yīng)信息并關(guān)閉連接塘慕。

這個是基于“分布式、無狀態(tài)蒂胞、基于TCP的請求/響應(yīng)式”的http協(xié)議的图呢。

文字demo

客戶端:啦啦啦,有沒有新信息(Request)
服務(wù)端:沒有(Response)
客戶端:啦啦啦骗随,有沒有新信息(Request)
服務(wù)端:沒有蛤织。。(Response)
客戶端:啦啦啦鸿染,有沒有新信息(Request)
服務(wù)端:你好煩啊指蚜,沒有啊。牡昆。(Response)
客戶端:啦啦啦姚炕,有沒有新消息(Request)
服務(wù)端:好啦好啦,有啦給你丢烘。(Response)
客戶端:啦啦啦柱宦,有沒有新消息(Request)
服務(wù)端:。播瞳。掸刊。。赢乓。沒忧侧。石窑。。蚓炬。沒松逊。。肯夏。沒有(Response) ---- loop

代碼demo

<script type="text/javascript">
    //前端Ajax持續(xù)調(diào)用服務(wù)端经宏,稱為Ajax輪詢技術(shù)
    var getting = {
        url:'server.php',
        dataType:'json',
        success:function(res) {
            console.log(res);
            $.ajax(getting); //關(guān)鍵在這里,回調(diào)函數(shù)內(nèi)再次請求Ajax
        }        
        //當請求時間過長(默認為60秒)驯击,就再次調(diào)用ajax長輪詢
        error:function(res){
            $.ajax($getting);
        }
    };
    $.ajax(getting);
</script>

Comet長輪詢烁兰,一種hack技術(shù)

客戶端向服務(wù)器發(fā)送Ajax請求,服務(wù)器接到請求后hold住連接徊都,直到有新消息才返回響應(yīng)信息并關(guān)閉連接沪斟,客戶端處理完響應(yīng)信息后再向服務(wù)器發(fā)送新的請求。

Comet的實現(xiàn)主要有兩種方式暇矫,基于Ajax的長輪詢(long-polling)方式和基于 Iframe 及 htmlfile 的流(http streaming)方式主之。

Ajax的長輪詢:

Ajax的長輪詢

基于Iframe的流:

在頁面中嵌入一個隱藏的iframe,然后讓這個iframe的src屬性指向我們請求的一個服務(wù)端地址,并且為了數(shù)據(jù)更新袱耽,我們將頁面上數(shù)據(jù)更新操作封裝為一個js函數(shù)杀餐,將函數(shù)名當做參數(shù)傳遞到這個地址當中。

服務(wù)端收到請求后解析地址取出參數(shù)(客戶端js函數(shù)調(diào)用名)朱巨,每當有數(shù)據(jù)更新的時候史翘,返回對客戶端函數(shù)的調(diào)用,并且將要跟新的數(shù)據(jù)以js函數(shù)的參數(shù)填入到返回內(nèi)容當中冀续,例如返回“<script type="text/javascript">update("data")</script>”這樣一個字符串琼讽,意味著以data為參數(shù)調(diào)用客戶端update函數(shù)進行客戶端view更新。

基于Iframe的流

文字demo

客戶端:啦啦啦洪唐,有沒有新信息钻蹬,沒有的話就等有了才返回給我吧(Request)
服務(wù)端:額。凭需。 等待到有消息的時候问欠。。來 給你(Response)
客戶端:啦啦啦粒蜈,有沒有新信息顺献,沒有的話就等有了才返回給我吧(Request) -loop

代碼demo

<script type="text/javascript">
    //前端Ajax持續(xù)調(diào)用服務(wù)端,稱為Ajax輪詢技術(shù)
    var getting = {
        url:'server.php',
        dataType:'json',
        success:function(res) {
            console.log(res);
            $.ajax(getting); //關(guān)鍵在這里枯怖,回調(diào)函數(shù)內(nèi)再次請求Ajax
        }        
        //當請求時間過長(默認為60秒)注整,就再次調(diào)用ajax長輪詢
        error:function(res){
            $.ajax($getting);
        }
    };
    $.ajax(getting);
</script>

websocket

文字demo

客戶端:啦啦啦,我要建立Websocket協(xié)議,需要的服務(wù):chat肿轨,Websocket協(xié)議版本:17(HTTP Request)
服務(wù)端:ok寿冕,確認,已升級為Websocket協(xié)議(HTTP Protocols Switched)
客戶端:麻煩你有信息的時候推送給我噢椒袍。驼唱。
服務(wù)端:ok,有的時候會告訴你的槐沼。
服務(wù)端:balabalabalabala
服務(wù)端:balabalabalabala
服務(wù)端:哈哈哈哈哈啊哈哈哈哈
客戶端:麻煩你有信息的時候推送給我噢曙蒸。。
服務(wù)端:笑死我了哈哈哈哈哈哈哈

代碼demo

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function (evt) {
    console.log("Connection open ...");
    ws.send("Hello WebSockets!");
};

ws.onmessage = function (evt) {
    console.log("Received Message: " + evt.data);
    ws.close();
};

ws.onclose = function (evt) {
    console.log("Connection closed.");
};

SSE(Server-Sent Event)

所謂SSE岗钩,就是瀏覽器向服務(wù)器發(fā)送一個HTTP請求,然后服務(wù)器不斷單向地向瀏覽器推送“信息”(message)肖油。這種信息在格式上很簡單兼吓、固定,就是“信息”加上前綴“data: ”森枪,然后以“\n\n”結(jié)尾视搏。

SSE 是一種僅使用 HTTP 傳送異步消息的 HTML5 標準。不同于 WebSocket县袱,SSE 不需要在后端創(chuàng)建服務(wù)器套接字浑娜。

后端響應(yīng)需加入頭信息:response.headers["Content-Type"] = "text/event-stream"。

支持的事件有:

onopen 當通往服務(wù)器的連接被打開
onmessage 當接收到消息
onerror 當發(fā)生錯誤

EventSource.close()來關(guān)閉連接式散。

兼容性:https://developer.mozilla.org/zh-CN/docs/Server-sent_events/EventSource
IE全系不支持筋遭。

文字demo

SSE是單向通道, 只能服務(wù)端向瀏覽器發(fā)送數(shù)據(jù)暴拄。特別適用于客戶端只需接收從服務(wù)器傳入的更新的應(yīng)用程序漓滔。

客戶端:啦啦啦,我要建立SSE
服務(wù)端:ok乖篷,有的時候會告訴你的响驴。
服務(wù)端:來了來了,有消息了
服務(wù)端:balabalabalabala
服務(wù)端:哈哈哈哈哈啊哈哈哈哈
服務(wù)端:笑死我了哈哈哈哈哈哈哈

代碼demo

if (typeof (EventSource) !== "undefined") {
    var source = new EventSource("server.php");
    source.onopen = function () {
        console.log("Connection to server opened.");
    };
    source.onmessage = function (event) {

        document.getElementById("result").innerHTML += event.data + "<br>";
    };
    source.onerror = function () {
        console.log("EventSource failed.");
    };
} else {
    document.getElementById("result").innerHTML = "抱歉撕蔼,你的瀏覽器不支持 server-sent 事件...";
}

選擇

目前豁鲤,我們已經(jīng)積累了較為豐富輪詢請求經(jīng)驗。但是鲸沮,輪詢琳骡、長輪詢已經(jīng)無法滿足這次需求。主要原因是:燈態(tài)數(shù)據(jù)是500ms上報一次诉探,頻次非常高日熬,輪詢不適合,有請求丟失和異步跳秒的風險。而且竖席,一般而言輪詢都有無謂請求耘纱、浪費帶寬、效率低下的問題毕荐。所以需要從SSE束析、WebSocket方案中選擇。SSE憎亚、WebSocket優(yōu)劣比較如下:

SSE WebSocket
通信類型 半雙工(單向) 全雙工(雙向)
瀏覽器支持 目前在 Microsoft 瀏覽器中不可用员寇。 可用于所有主要瀏覽器。
開發(fā)工作量 械诿馈:只需發(fā)送一條包含特定標頭的 HTTP 消息蝶锋。 中等:需要建立并維護 TCP 套接字通信。在服務(wù)器端還需要一個監(jiān)聽器套接字什往。
擴展性 較弱 較強扳缕,支持數(shù)據(jù)的雙向通信

為了后期更好的擴展性,選擇了websocket的方案别威。

深入websocket

簡單理解

WebSocket 協(xié)議在2008年誕生薪寓,2011年成為國際標準钳宪。所有現(xiàn)代瀏覽器都已經(jīng)支持了。

它的最大特點就是,服務(wù)器可以主動向客戶端推送信息幸斥,客戶端也可以主動向服務(wù)器發(fā)送信息恕汇,是真正的雙向平等對話爽柒,屬于服務(wù)器推送技術(shù)的一種蹋订。

http、websocket流程圖

特點:

  1. 建立在 TCP 協(xié)議之上科侈,服務(wù)器端的實現(xiàn)比較容易载佳。

  2. 與 HTTP 協(xié)議有著良好的兼容性。默認端口也是80和443臀栈,并且握手階段采用 HTTP 協(xié)議蔫慧,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務(wù)器权薯。

  3. 數(shù)據(jù)格式比較輕量姑躲,性能開銷小,通信高效盟蚣。

  4. 可以發(fā)送文本黍析,也可以發(fā)送二進制數(shù)據(jù)。

  5. 沒有同源限制屎开,客戶端可以與任意服務(wù)器通信阐枣。

  6. 協(xié)議標識符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL蔼两。

ws://example.com:80/some/path

客戶端實現(xiàn)與API簡介

包括ie在內(nèi)的所有主流瀏覽器都支持websocket甩鳄。

  • 構(gòu)造函數(shù) WebSocket(url[, protocols]) 返回一個 WebSocket 對象
  • 屬性
    • WebSocket.binaryType 使用二進制的數(shù)據(jù)類型連接 blob(Blob 對象表示一個不可變、原始數(shù)據(jù)的類文件對象额划。)妙啃、arrayBuffer
    • WebSocket.bufferedAmount 只讀 未發(fā)送至服務(wù)器的字節(jié)數(shù)
    • WebSocket.extensions 只讀 服務(wù)器選擇的擴展
    • WebSocket.onclose 用于指定連接關(guān)閉后的回調(diào)函數(shù)
    • WebSocket.onerror 用于指定連接失敗后的回調(diào)函數(shù)
    • WebSocket.onmessage 用于指定當從服務(wù)器接受到信息時的回調(diào)函數(shù)
    • WebSocket.onopen 用于指定連接成功后的回調(diào)函數(shù)
    • WebSocket.protocol 只讀 服務(wù)器選擇的下屬協(xié)議
    • WebSocket.readyState 只讀 當前的鏈接狀態(tài)
    • WebSocket.url 只讀 WebSocket 的絕對路徑
  • 方法
    • WebSocket.close([code[, reason]]) 關(guān)閉當前鏈接
    • WebSocket.send(data) 向服務(wù)器發(fā)送數(shù)據(jù)

瀏覽器客戶端示例代碼:

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function (evt) {
    console.log("Connection open ...");
    ws.send("Hello WebSockets!");
};

ws.onmessage = function (evt) {
    console.log("Received Message: " + evt.data);
    ws.close();
};

ws.onclose = function (evt) {
    console.log("Connection closed.");
};

服務(wù)端的實現(xiàn)

幾乎各種后端語言都有對應(yīng)的實現(xiàn)方法,支持度較好俊戳。

常用的 Node 實現(xiàn)有以下三種揖赴。

代碼略過,直接到以上項目的GitHub中查看即可抑胎。

nginx的支持

在配置 HTTP燥滑、HTTPS 域名位置加入如下配置:

location /websocket {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

Nginx 自從 1.3 版本就開始支持 WebSocket 了,并且可以為 WebSocket 應(yīng)用程序做反向代理和負載均衡圆恤。

WebSockets 受到 Nginx 缺省為60秒的 proxy_read_timeout 的影響突倍。這意味著,如果你有一個程序使用了 WebSocket盆昙,但又可能超過60秒不發(fā)送任何數(shù)據(jù)的話,那你要么需要增加超時時間焊虏,要么實現(xiàn)一個 ping 的消息(心跳報文)以保持聯(lián)系淡喜。使用 ping 的解決方法有額外的好處,可以發(fā)現(xiàn)連接是否被意外關(guān)閉诵闭。

深入理解

websocket到底是什么炼团?

概念:

HTTP是運行在TCP協(xié)議傳輸層上的應(yīng)用協(xié)議,而WebSocket是通過HTTP協(xié)議協(xié)商如何連接疏尿,然后獨立運行在TCP協(xié)議傳輸層上的應(yīng)用協(xié)議瘟芝。

WebSocket僅僅是利用了HTTP協(xié)議做連接請求。WebSocket相當于一個簡化版的TCP傳輸子層(實際上WebSocket也是應(yīng)用層協(xié)議)褥琐。

WebSocket之所以能持久連接原因是它運行在TCP協(xié)議上锌俱,TCP協(xié)議自身是長連接協(xié)議,所以WebSocket當然可以長連接敌呈。為什么HTTP不是長連接贸宏,原因是早期的HTTP在發(fā)起每個請求,響應(yīng)完成后就會關(guān)閉Socket磕洪。但是后來加了多路復(fù)用KeepAlive協(xié)議后HTTP協(xié)議已經(jīng)可以實現(xiàn)長連接了吭练,可以處理長連接事務(wù)了。

所以析显,Websocket是一個持久化的協(xié)議鲫咽。

特別地:

WebSocket 不是 HTML5 的東西。

WebSocket 是一個協(xié)議,歸屬于 IETF分尸。WebSocket API 是一個 Web API锦聊,歸屬于 W3C。兩個規(guī)范是獨立發(fā)布的寓落。

廣義上的 HTML5 是一個很寬廣的概念括丁,是對大量新 API 的總稱, 里面包含的是 WebSocket API伶选,并不是 WebSocket史飞。簡單的說,可以把 WebSocket 當成 HTTP仰税,WebSocket API 當成 Ajax构资。

原理及運行機制

wesocket協(xié)議流程圖:

wesocket協(xié)議流程圖
wesocket協(xié)議流程圖

Websocket借用HTTP的協(xié)議來完成一部分握手。

典型的Websocket的http握手部分:

1.請求部分

GET ws://xxx.xx.xx.xx:8000/v2x-omp/websocket HTTP/1.1
Host: xxx.xx.xx.xx:8000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: http://xxx.xx.xx.xx:8000
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: _uid=120070472.3333; x_xx=_QP5elb46q2p33h9Qm
Sec-WebSocket-Key: Uk07fY3CxNYoq2N5Fl9l1A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

和一般http協(xié)議不同的主要有:

(1)

Upgrade: websocket
Connection: Upgrade

這個是Websocket的核心陨簇,告訴Apache吐绵、Nginx等服務(wù)器:這邊發(fā)起的是Websocket協(xié)議,請用相應(yīng)的后端來處理河绽。

(2)

Sec-WebSocket-Key: Uk07fY3CxNYoq2N5Fl9l1A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Version: 13

Sec-WebSocket-Key 是一個Base64 encode的值己单,這個是瀏覽器隨機生成的,用于驗證交互的服務(wù)器耙饰。

Sec-WebSocket-Version 是告訴服務(wù)器所使用的Websocket Draft(協(xié)議版本)纹笼,避免因版本不同出現(xiàn)兼容性問題。

2.響應(yīng)部分

服務(wù)器會響應(yīng)如下苟跪,成功建立Websocket廷痘。

HTTP/1.1 101 Switching Protocols
Server: nginx
Date: Tue, 02 Apr 2019 08:11:57 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: khI5KCJzpRnpR8H2sOx+nnGCDAY=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15

至此,HTTP已經(jīng)完成它所有工作了--連接握手成功件已,接下來就是完全按照Websocket協(xié)議進行了笋额。

websocket傳輸幀協(xié)議:

websocket傳輸幀協(xié)議

參考文檔

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

http://www.ruanyifeng.com/blog/2017/05/websocket.html

https://www.zhihu.com/question/20215561

https://blog.51cto.com/kusorz/2058591?utm_source=oschina-app

https://zhuanlan.zhihu.com/p/21595082

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市篷扩,隨后出現(xiàn)的幾起案子兄猩,更是在濱河造成了極大的恐慌,老刑警劉巖瞻惋,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厦滤,死亡現(xiàn)場離奇詭異,居然都是意外死亡歼狼,警方通過查閱死者的電腦和手機掏导,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羽峰,“玉大人趟咆,你說我怎么就攤上這事添瓷。” “怎么了值纱?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵鳞贷,是天一觀的道長。 經(jīng)常有香客問我虐唠,道長搀愧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任疆偿,我火速辦了婚禮咱筛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杆故。我一直安慰自己迅箩,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布处铛。 她就那樣靜靜地躺著饲趋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撤蟆。 梳的紋絲不亂的頭發(fā)上奕塑,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音家肯,去河邊找鬼爵川。 笑死,一個胖子當著我的面吹牛息楔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扒披,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼值依,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碟案?” 一聲冷哼從身側(cè)響起愿险,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎价说,沒想到半個月后辆亏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鳖目,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年扮叨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片领迈。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡彻磁,死狀恐怖碍沐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衷蜓,我是刑警寧澤累提,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站磁浇,受9級特大地震影響斋陪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜置吓,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一无虚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧交洗,春花似錦骑科、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至置森,卻和暖如春斗埂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凫海。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工呛凶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人行贪。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓漾稀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親建瘫。 傳聞我的和親對象是個殘疾皇子崭捍,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容