Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文作者網(wǎng)易智慧企業(yè)web前端開發(fā)工程師馬瑩瑩。為了提升內(nèi)容質(zhì)量,收錄時有修訂和改動。

          1、引言

          在一個完善的即時通訊IM應(yīng)用中,WebSocket是極其關(guān)鍵的一環(huán),它為基于Web的即時通訊應(yīng)用提供了一種全雙工的通信機(jī)制。但為了提升IM等實(shí)際應(yīng)用場景下的消息即時性和可靠性,我們需要克服WebSocket及其底層依賴的TCP連接對于復(fù)雜網(wǎng)絡(luò)情況下的不穩(wěn)定性,即時通訊的開發(fā)者們通常都需要為其設(shè)計一套完整的連接保活、驗(yàn)活以及斷片網(wǎng)重連方案。

          就斷網(wǎng)重連而言,其重連響應(yīng)速度將嚴(yán)重影響了上層應(yīng)用的“即時性”和用戶體驗(yàn)。試想打開網(wǎng)絡(luò)一分鐘后,微信的網(wǎng)絡(luò)不能即時感知到socket連接的恢復(fù),無法即時收發(fā)聊天消息的話,是不是很崩潰?

          因此,如何在復(fù)雜網(wǎng)絡(luò)場景下,更即時快速地感知網(wǎng)絡(luò)變動,并快速恢復(fù)WebSocket的可用性,就變得尤為重要。本文將基于筆者的開發(fā)實(shí)踐,分享WebSocket在不同狀態(tài)下、不同的網(wǎng)絡(luò)狀態(tài)下,應(yīng)該如何實(shí)現(xiàn)快速斷網(wǎng)重連。

          * 閱讀對象:本文適合有過IM底層網(wǎng)絡(luò)實(shí)際開發(fā)經(jīng)驗(yàn),或者對底層網(wǎng)絡(luò)實(shí)現(xiàn)有較深了解的開發(fā)者閱讀。如果對底層網(wǎng)絡(luò)了解甚少,建議跳過本文,直接閱讀網(wǎng)絡(luò)本文末尾附錄部分的基礎(chǔ)后再回頭來看。

          * 內(nèi)容點(diǎn)評:本文內(nèi)容沒有高大上,但比較干貨,實(shí)用性較高,內(nèi)容也很通俗,建議可詳細(xì)閱讀。文中雖講的是WebSocket,但思想可以延伸應(yīng)用到基于TCP協(xié)議的同類技術(shù)中。

          本文已同步發(fā)布于“即時通訊技術(shù)圈”公眾號,歡迎關(guān)注:

          ▲ 本文在公眾號上的鏈接是:點(diǎn)此進(jìn)入原文鏈接是:http://www.52im.net/thread-3098-1-1.html

          2、預(yù)備知識

          本文中將要分享的內(nèi)容是基于實(shí)踐總結(jié),如果你對Web端的即時通訊知識還一頭霧水,務(wù)必先讀:《新手入門貼:史上最全Web端即時通訊技術(shù)原理詳解》、《Web端即時通訊技術(shù)盤點(diǎn):短輪詢、Comet、Websocket、SSE》。

          限于篇幅,本文不會深究WebSocket技術(shù)細(xì)節(jié),如有興趣請系統(tǒng)學(xué)習(xí):

          3、快速了解WebSocket

          Websocket誕生于2008年,在2011年成為國際標(biāo)準(zhǔn),現(xiàn)在所有的瀏覽器都已支持(詳見《新手快速入門:WebSocket簡明教程》)。它是一種全新的應(yīng)用層協(xié)議,是專門為web客戶端和服務(wù)端設(shè)計的真正的全雙工通信協(xié)議,可以類比HTTP協(xié)議來了解websocket協(xié)議。

          圖片引用自《WebSocket詳解(四):刨根問底HTTP與WebSocket的關(guān)系(上篇)

          它們的不同點(diǎn):

          • 1)HTTP的協(xié)議標(biāo)識符是http,WebSocket的是ws;
          • 2)HTTP請求只能由客戶端發(fā)起,服務(wù)器無法主動向客戶端推送消息,而WebSocket可以;
          • 3)HTTP請求有同源限制,不同源之間通信需要跨域,而WebSocket沒有同源限制。

          它們的相同點(diǎn):

          • 1)都是應(yīng)用層的通信協(xié)議;
          • 2)默認(rèn)端口一樣,都是80或443;
          • 3)都可以用于瀏覽器和服務(wù)器間的通信;
          • 4)都基于TCP協(xié)議。

          兩者和TCP的關(guān)系圖:

          圖片引用自《新手快速入門:WebSocket簡明教程

          有關(guān)Http和WebSocket的關(guān)系,可以詳讀:

          有關(guān)WebSocket和Socket的關(guān)系,可以詳讀:《WebSocket詳解(六):刨根問底WebSocket與Socket的關(guān)系》.

          4、WebSocket重連過程拆解

          首先考慮一個問題,何時需要重連?

          最容易想到的是WebSocket連接斷了,為了接下來能收發(fā)消息,我們需要再發(fā)起一次連接。

          但在很多場景下,即便WebSocket連接沒有斷開,實(shí)際上也不可用了。

          比如以下場景:

          • 1)設(shè)備切換網(wǎng)絡(luò);
          • 2)鏈路中間路由崩潰(常識是一條socket連接對應(yīng)的網(wǎng)絡(luò)通路上,會存在很多路由設(shè)備);
          • 3)鏈路的前端出口不可用(比如家庭WiFi中,網(wǎng)絡(luò)連接正常,但實(shí)際運(yùn)營商的寬帶已經(jīng)欠費(fèi)被停機(jī));
          • 4)服務(wù)器負(fù)載持續(xù)過高無法響應(yīng)等。

          這些場景下的WebSocket都沒有斷開,但對上層來說,都沒辦法正常的收發(fā)數(shù)據(jù)了。

          因此在重連前,我們需要一種機(jī)制來感知連接是否可用、服務(wù)是否可用,而且要能快速感知,以便能夠快速從不可用狀態(tài)中恢復(fù)。

          一旦感知到了連接不可用,那便可以棄舊圖新了,棄用并斷開舊連接,然后發(fā)起一次新連接。這兩個步驟看似簡單,但若想達(dá)到快,且不是那么容易的。

          首先:是斷開舊連接,對客戶端來說,如何快速斷開?協(xié)議規(guī)定客戶端必須要和服務(wù)器協(xié)商后才能斷開WebSocket連接,但是當(dāng)客戶端已經(jīng)聯(lián)系不上服務(wù)器、無法協(xié)商時,如何斷開并快速恢復(fù)?

          其次:是快速發(fā)起新連接。此快非彼快,這里的快并非是立即發(fā)起連接,立即發(fā)起連接會對服務(wù)器帶來不可預(yù)估的影響。重連時通常會采用一些退避算法,延遲一段時間后再發(fā)起重連。但如何在重連間隔和性能消耗間做出權(quán)衡?如何在“恰當(dāng)?shù)臅r間點(diǎn)”快速發(fā)起連接?

          帶著這些疑問,我們來細(xì)看下這三個過程:

          5、快速重連關(guān)鍵1:快速感知何時需要重連

          5.1 場景

          需要重連的場景可以細(xì)分為三種:

          • 1)連接明確斷開了;
          • 2)連接沒斷但是不可用了;
          • 3)連接對端的服務(wù)不可用了。

          對于第一種場景:這很簡單,連接直接斷開了,肯定需要重連了。

          對于后兩者:無論是連接不可用,還是服務(wù)不可用,對上層應(yīng)用的影響都是不能再收發(fā)即時消息了。

          5.2 心跳包主動探測網(wǎng)絡(luò)可用性

          所以從上面這個角度出發(fā),感知何時需要重連的一種簡單粗暴的方法就是通過心跳包超時:發(fā)送一個心跳包,如果超過特定的時間后還沒有收到服務(wù)器回包,則認(rèn)為服務(wù)不可用,如下圖中左側(cè)的方案(這種方法最直接)。

          那如果想要快速感知呢,就只能多發(fā)心跳包,加快心跳頻率。但是心跳太快對移動端流量、電量的消耗又會太多,所以使用這種方法沒辦法做到快速感知,可以作為檢測連接和服務(wù)可用的兜底機(jī)制。

          5.3 被動監(jiān)聽網(wǎng)絡(luò)狀態(tài)改變

          如果要檢測連接不可用,除了用心跳檢測,還可以通過判斷網(wǎng)絡(luò)狀態(tài)來實(shí)現(xiàn),因?yàn)閿嗑W(wǎng)、切換wifi、切換網(wǎng)絡(luò)是導(dǎo)致連接不可用的最直接原因,所以在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline時,大多數(shù)情況下需要重連下,但也不一定,因?yàn)閣ebscoket底層是基于TCP的,TCP連接不能敏銳的感知到應(yīng)用層的網(wǎng)絡(luò)變化,所以有時候即便網(wǎng)絡(luò)斷開了一小會,對WebSocket連接是不會有影響的,網(wǎng)絡(luò)恢復(fù)后,仍然能夠正常地進(jìn)行通信。

          因此在網(wǎng)絡(luò)由斷開到連接上時,立即判斷下連接是否可用,可以通過發(fā)一個心跳包判斷,如果能夠正常收到服務(wù)器的心跳回包,則說明連接仍是可用的,如果等待超時后仍沒有收到心跳回包,則需要重連,如上圖中的右側(cè)。這種方法的優(yōu)點(diǎn)是速度快,在網(wǎng)絡(luò)恢復(fù)后能夠第一時間感知連接是否可用,不可用的話可以快速執(zhí)行恢復(fù),但它只能覆蓋應(yīng)用層網(wǎng)絡(luò)變化導(dǎo)致WebSocket不可用的情況。

          5.4 小結(jié)

          綜上所述:

          • 1)定時發(fā)送心跳包檢測的方案貴在穩(wěn)定,能夠覆蓋所有場景,但速度不即時(心跳間隔是固定的);
          • 2)判斷網(wǎng)絡(luò)狀態(tài)的方案速度快,無需等待心跳間隔,較為靈敏,但覆蓋場景較為局限。

          因此,我們可以結(jié)合兩種方案:

          • 1)定時以不太快的頻率發(fā)送心跳包,比如40s/次、60s/次等,具體可以根據(jù)應(yīng)用場景來定;
          • 2)然后在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline時立即發(fā)送一次心跳,檢測當(dāng)前連接是否可用,不可用的話立即進(jìn)行恢復(fù)處理。

          這樣在大多數(shù)情況下,上層的應(yīng)用通信都能較快從不可用狀態(tài)中恢復(fù),對于少部分場景,有定時心跳作為兜底,在一個心跳周期內(nèi)也能夠恢復(fù)。

          6、快速重連關(guān)鍵2:快速斷開舊連接

          通常情況下,在發(fā)起下一次連接前,如果舊連接還存在的話,應(yīng)該先把舊連接斷開。

          這樣做的目的:

          • 1)一來可以釋放客戶端和服務(wù)器的資源;
          • 2)二來可以避免之后誤從舊連接收發(fā)數(shù)據(jù)。

          我們知道WebSocket底層是基于TCP協(xié)議傳輸數(shù)據(jù)的,連接兩端分別是服務(wù)器和客戶端,而TCP的TIME_WAIT狀態(tài)是由服務(wù)器端維持的,因此在大多數(shù)正常情況下,應(yīng)該由服務(wù)器發(fā)起斷開底層TCP連接,而不是客戶端。

          也就是說:

          • 1)要斷開WebSocket連接時,如果是服務(wù)器收到指示要斷開WebSocket,那它應(yīng)該立即發(fā)起斷開TCP連接;
          • 2)如果是客戶端收到指示要斷開WebSocket,那它應(yīng)該發(fā)信號給服務(wù)器,然后等待底層TCP連接被服務(wù)器斷開或直至超時。

          那如果客戶端想要斷開舊的WebSocket,可以分為WebSocket連接可用和不可用兩種情況來討論。

          具體如下:

          • 1)當(dāng)舊連接可用時,客戶端可以直接給服務(wù)器發(fā)送斷開信號,然后服務(wù)器發(fā)起斷開連接即可;
          • 2)當(dāng)舊連接不可用時,比如客戶端切換了wifi,客戶端發(fā)送了斷開信號,但是服務(wù)器收不到,客戶端只能遲遲等待,直至超時才能被允許斷開。

          超時斷開的過程相對來說是比較久的,那有沒有辦法可以快點(diǎn)斷開?

          上層應(yīng)用無法改變只能由服務(wù)器發(fā)起斷開連接這種協(xié)議層面的規(guī)則,所以只能從應(yīng)用邏輯入手,比如在上層通過業(yè)務(wù)邏輯保證舊連接完全失效,模擬連接斷開,然后在發(fā)起新連接,恢復(fù)通訊。

          這種方法相當(dāng)于嘗試斷開舊連接不行時,直接棄之,然后就能快速進(jìn)入下一流程,所以在使用時一定要確保在業(yè)務(wù)邏輯上舊連接已完全失效。

          比如:

          • 1)保證丟掉從舊連接收到所有數(shù)據(jù);
          • 2)舊連接不能阻礙新連接的建立
          • 3)舊連接超時斷開后不能影響新連接和上層業(yè)務(wù)邏輯等等。

          7、快速重連關(guān)鍵3:快速發(fā)起新連接

          有IM開發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該有所了解,遇到因網(wǎng)絡(luò)原因?qū)е碌闹剡B時,是萬萬不能立即發(fā)起一次新連接的,否則當(dāng)出現(xiàn)網(wǎng)絡(luò)抖動時,所有的設(shè)備都會立即同時向服務(wù)器發(fā)起連接,這無異于黑客通過發(fā)起大量請求消耗網(wǎng)絡(luò)帶寬引起的拒絕服務(wù)攻擊,這對服務(wù)器來說簡直是災(zāi)難(即:服務(wù)端雪崩效應(yīng))。

          所以在重連時通常采用一些退避算法,延遲一段時間再發(fā)起重連,如下圖中左側(cè)的流程。

          如果要快速連上呢?最直接的做法就是縮短重試間隔,重試間隔越短,在網(wǎng)絡(luò)恢復(fù)后就能越快的恢復(fù)通訊。但是太頻繁的重試對性能、帶寬、電量的消耗就比較嚴(yán)重。

          如何在這之間做一個較好的權(quán)衡呢?

          • 1)一種比較合理的方式是隨著重試次數(shù)增多,逐漸增大重試間隔;
          • 2)另一方面監(jiān)聽網(wǎng)絡(luò)變化,在網(wǎng)絡(luò)狀態(tài)由offline變?yōu)閛nline這種比較可能重連上的時刻,適當(dāng)?shù)販p小重連間隔。

          上述第2)種方案,如上圖中的右側(cè)所示,隨重試次數(shù)的增多,重連間隔也會變大。這兩種方式配合使用,更為合理。

          除此之外,還可以結(jié)合業(yè)務(wù)邏輯,根據(jù)成功重連上的可能性適當(dāng)?shù)恼{(diào)整間隔,如網(wǎng)絡(luò)未連接時或應(yīng)用在后臺時重連間隔可以調(diào)大一些,網(wǎng)絡(luò)正常的狀態(tài)下可以適當(dāng)調(diào)小一些等等,加快重連上的速度。

          8、本文小結(jié)

          最后總結(jié)一下。

          本文將WebSocket斷網(wǎng)重連邏輯細(xì)分為三個步驟:

          • 1)確定何時需要重連;
          • 2)斷開舊連接;
          • 3)發(fā)起新連接。

          然后分別分析了在WebSocket的不同狀態(tài)下、不同的網(wǎng)絡(luò)狀態(tài)下,如何快速完成這個三個步驟。

          過程具體總結(jié)就是:

          • 1)首先:通過定時發(fā)送心跳包的方式檢測當(dāng)前連接是否可用,同時監(jiān)測網(wǎng)絡(luò)恢復(fù)事件,在恢復(fù)后立即發(fā)送一次心跳,快速感知當(dāng)前狀態(tài),判斷是否需要重連;
          • 2)其次:正常情況下由服務(wù)器斷開舊連接,與服務(wù)器失去聯(lián)系時直接棄用舊連接,上層模擬斷開,來實(shí)現(xiàn)快速斷開;
          • 3)最后:發(fā)起新連接時使用退避算法延遲一段時間再發(fā)起連接,同時考慮到資源浪費(fèi)和重連速度,可以在網(wǎng)絡(luò)離線時調(diào)大重連間隔,在網(wǎng)絡(luò)正常或網(wǎng)絡(luò)由offline變?yōu)閛nline時縮小重連間隔,使之盡可能快地重連上。

          以上就是我關(guān)于如何實(shí)現(xiàn)WebSocket快速重連的技術(shù)分享,歡迎留言與我探討。

          9、參考資料

          [1] RFC 6455 文檔

          [2] 新手快速入門:WebSocket簡明教程

          [3] WebSocket詳解(四):刨根問底HTTP與WebSocket的關(guān)系(上篇)

          [4] WebSocket詳解(五):刨根問底HTTP與WebSocket的關(guān)系(下篇)

          [5] WebSocket詳解(六):刨根問底WebSocket與Socket的關(guān)系

          附錄:更多Web端即時通訊資料

          新手入門貼:史上最全Web端即時通訊技術(shù)原理詳解

          Web端即時通訊技術(shù)盤點(diǎn):短輪詢、Comet、Websocket、SSE

          SSE技術(shù)詳解:一種全新的HTML5服務(wù)器推送事件技術(shù)

          Comet技術(shù)詳解:基于HTTP長連接的Web端實(shí)時通信技術(shù)

          socket.io實(shí)現(xiàn)消息推送的一點(diǎn)實(shí)踐及思路

          LinkedIn的Web端即時通訊實(shí)踐:實(shí)現(xiàn)單機(jī)幾十萬條長連接

          Web端即時通訊技術(shù)的發(fā)展與WebSocket、Socket.io的技術(shù)實(shí)踐

          Web端即時通訊安全:跨站點(diǎn)WebSocket劫持漏洞詳解(含示例代碼)

          開源框架Pomelo實(shí)踐:搭建Web端高性能分布式IM聊天服務(wù)器

          使用WebSocket和SSE技術(shù)實(shí)現(xiàn)Web端消息推送

          詳解Web端通信方式的演進(jìn):從Ajax、JSONP 到 SSE、Websocket

          MobileIMSDK-Web的網(wǎng)絡(luò)層框架為何使用的是Socket.io而不是Netty?

          理論聯(lián)系實(shí)際:從零理解WebSocket的通信原理、協(xié)議格式、安全性

          微信小程序中如何使用WebSocket實(shí)現(xiàn)長連接(含完整源碼)

          八問WebSocket協(xié)議:為你快速解答WebSocket熱門疑問

          快速了解Electron:新一代基于Web的跨平臺桌面技術(shù)

          一文讀懂前端技術(shù)演進(jìn):盤點(diǎn)Web前端20年的技術(shù)變遷史

          Web端即時通訊基礎(chǔ)知識補(bǔ)課:一文搞懂跨域的所有問題!

          Web端即時通訊實(shí)踐干貨:如何讓你的WebSocket斷網(wǎng)重連更快速?

          >> 更多同類文章 ……

          (本文同步發(fā)布于:http://www.52im.net/thread-3098-1-1.html



          作者:Jack Jiang (點(diǎn)擊作者姓名進(jìn)入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發(fā)交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 鹤庆县| 永顺县| 昌图县| 绿春县| 英德市| 五家渠市| 乃东县| 宜君县| 阳山县| 南丰县| 揭阳市| 正镶白旗| 宝鸡市| 汶上县| 山阴县| 楚雄市| 阜平县| 郓城县| 聂荣县| 饶阳县| 怀远县| 蓬溪县| 双江| 铁岭县| 长丰县| 临澧县| 湖北省| 黄龙县| 关岭| 黎城县| 称多县| 弥渡县| 赤水市| 凤山县| 东乡县| 社会| 高台县| 绥宁县| 潞城市| 布拖县| 乌兰察布市|