天天看點

為什麼有HTTP協定,還要有websocket協定

平時我們打開網頁,比如購物網站某寶。都是點一下清單商品,跳轉一下網頁就到了商品詳情。

從HTTP協定的角度來看,就是點一下網頁上的某個按鈕,前端發一次HTTP請求,網站傳回一次HTTP響應。

這種由用戶端主動請求,伺服器響應的方式也滿足大部分網頁的功能場景。

但有沒有發現,這種情況下,伺服器從來就不會主動給用戶端發一次消息。

就像你喜歡的女生從來不會主動找你一樣。

但如果現在,你在刷網頁的時候右下角突然彈出一個小廣告,提示你【一個人在家偷偷才能玩哦】。

求知,好學,勤奮,這些刻在你DNA裡的東西都動起來了。

你點開後發現。

長相平平無奇的古某提示你”道士9條狗,全服橫着走”。

影帝某輝老師跟你說”系兄弟就來砍我”。

為什麼有HTTP協定,還要有websocket協定

來都來了,你就選了個角色進到了遊戲界面裡。

為什麼有HTTP協定,還要有websocket協定

這時候,上來就是一個小怪,從遠處走來,然後瘋狂拿木棒子抽你。

你全程沒點任何一次滑鼠。伺服器就自動将怪物的移動資料和攻擊資料源源不斷發給你了。

這….太暖心了。

感動之餘,問題就來了,

像這種看起來伺服器主動發消息給用戶端的場景,是怎麼做到的?

在真正回答這個問題之前,我們先來聊下一些相關的知識背景。

使用HTTP不斷輪詢

其實問題的痛點在于,怎麼樣才能在使用者不做任何操作的情況下,網頁能收到消息并發生變更。

最常見的解決方案是,網頁的前端代碼裡不斷定時發HTTP請求到伺服器,伺服器收到請求後給用戶端響應消息。

這其實時一種僞伺服器推的形式。

它其實并不是伺服器主動發消息到用戶端,而是用戶端自己不斷偷偷請求伺服器,隻是使用者無感覺而已。

用這種方式的場景也有很多,最常見的就是掃碼登入。

比如某信公衆号平台,登入頁面二維碼出現之後,前端網頁根本不知道使用者掃沒掃,于是不斷去向後端伺服器詢問,看有沒有人掃過這個碼。而且是以大概1到2秒的間隔去不斷送出請求,這樣可以保證使用者在掃碼後能在1到2s内得到及時的回報,不至于等太久。

為什麼有HTTP協定,還要有websocket協定

但這樣,會有兩個比較明顯的問題

  • 當你打開F12頁面時,你會發現滿屏的HTTP請求。雖然很小,但這其實也消耗帶寬,同時也會增加下遊伺服器的負擔。
  • 最壞情況下,使用者在掃碼後,需要等個1~2s,正好才觸發下一次http請求,然後才跳轉頁面,使用者會感到明顯的卡頓。

使用起來的體驗就是,二維碼出現後,手機掃一掃,然後在手機上點個确認,這時候卡頓等個1~2s,頁面才跳轉。

那麼問題又來了,有沒有更好的解決方案?

有,而且實作起來成本還非常低。

長輪詢

我們知道,HTTP請求發出後,一般會給伺服器留一定的時間做響應,比如3s,規定時間内沒傳回,就認為是逾時。

如果我們的HTTP請求将逾時設定的很大,比如30s,在這30s内隻要伺服器收到了掃碼請求,就立馬傳回給用戶端網頁。如果逾時,那就立馬發起下一次請求。

這樣就減少了HTTP請求的個數,并且由于大部分情況下,使用者都會在某個30s的區間内做掃碼操作,是以響應也是及時的。

為什麼有HTTP協定,還要有websocket協定

比如,某度雲網盤就是這麼幹的。是以你會發現一掃碼,手機上點個确認,電腦端網頁就秒跳轉,體驗很好。

真一舉兩得。

像這種發起一個請求,在較長時間内等待伺服器響應的機制,就是所謂的長輪詢機制。我們常用的消息隊列RocketMQ中,消費者去取資料時,也用到了這種方式。

為什麼有HTTP協定,還要有websocket協定

像這種,在使用者不感覺的情況下,伺服器将資料推送給浏覽器的技術,就是所謂的伺服器推送技術,它還有個毫不沾邊的英文名,comet技術,大家聽過就好。

上面提到的兩種解決方案,本質上,其實還是用戶端主動去取資料。

對于像掃碼登入這樣的簡單場景還能用用。

但如果是網頁遊戲呢,遊戲一般會有大量的資料需要從伺服器主動推送到用戶端。

這就得說下websocket了。

websocket是什麼

我們知道TCP連接配接的兩端,同一時間裡,雙方都可以主動向對方發送資料。這就是所謂的全雙工。

而現在使用最廣泛的HTTP1.1,也是基于TCP協定的,同一時間裡,用戶端和伺服器隻能有一方主動發資料,這就是所謂的半雙工。

也就是說,好好的全雙工TCP,被HTTP用成了半雙工。

為什麼?

這是由于HTTP協定設計之初,考慮的是看看網頁文本的場景,能做到用戶端發起請求再由伺服器響應,就夠了,根本就沒考慮網頁遊戲這種,用戶端和伺服器之間都要互相主動發大量資料的場景。

是以為了更好的支援這樣的場景,我們需要另外一個基于TCP的新協定。

于是新的應用層協定websocket就被設計出來了。

大家别被這個名字給帶偏了。雖然名字帶了個socket,但其實socket和websocket之間,就跟雷峰和雷峰塔一樣,二者接近毫無關系。

為什麼有HTTP協定,還要有websocket協定

怎麼建立websocket連接配接

我們平時刷網頁,一般都是在浏覽器上刷的,一會刷刷圖文,這時候用的是HTTP協定,一會打開網頁遊戲,這時候就得切換成我們新介紹的websocket協定。

為了相容這些使用場景。浏覽器在TCP三向交握建立連接配接之後,都統一使用HTTP協定先進行一次通信。

  • 如果此時是普通的HTTP請求,那後續雙方就還是老樣子繼續用普通HTTP協定進行互動,這點沒啥疑問。
  • 如果這時候是想建立websocket連接配接,就會在HTTP請求裡帶上一些特殊的header頭。
1
2
3
           
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg==\r\n
           

這些header頭的意思是,浏覽器想更新協定(Connection: Upgrade),并且想更新成websocket協定(Upgrade: websocket)。

同時帶上一段随機生成的base64碼(Sec-WebSocket-Key),發給伺服器。

如果伺服器正好支援更新成websocket協定。就會走websocket握手流程,同時根據用戶端生成的base64碼,用某個公開的算法變成另一段字元串,放在HTTP響應的 Sec-WebSocket-Accept 頭裡,同時帶上101狀态碼,發回給浏覽器。

1
2
3
4
           
HTTP/1.1 101 Switching Protocols\r\n
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
           
http狀态碼=200(正常響應)的情況,大家見得多了。101确實不常見,它其實是指協定切換。
為什麼有HTTP協定,還要有websocket協定

之後,浏覽器也用同樣的公開算法将base64碼轉成另一段字元串,如果這段字元串跟伺服器傳回來的字元串一緻,那驗證通過。

為什麼有HTTP協定,還要有websocket協定

就這樣經曆了一來一回兩次HTTP握手,websocket就建立完成了,後續雙方就可以使用webscoket的資料格式進行通信了。

為什麼有HTTP協定,還要有websocket協定

websocket抓包

我們可以用wireshark抓個包,實際看下資料包的情況。

為什麼有HTTP協定,還要有websocket協定

上面這張圖,注意畫了紅框的第2445行封包,是websocket的第一次握手,意思是發起了一次帶有特殊Header的HTTP請求。

為什麼有HTTP協定,還要有websocket協定

上面這個圖裡畫了紅框的4714行封包,就是伺服器在得到第一次握手後,響應的第二次握手,可以看到這也是個HTTP類型的封包,傳回的狀态碼是101。同時可以看到傳回的封包header中也帶有各種websocket相關的資訊,比如Sec-WebSocket-Accept。

為什麼有HTTP協定,還要有websocket協定

上面這張圖就是全貌了,從截圖上的注釋可以看出,websocket和HTTP一樣都是基于TCP的協定。經曆了三次TCP握手之後,利用HTTP協定更新為websocket協定。

你在網上可能會看到一種說法:”websocket是基于HTTP的新協定”,其實這并不對,因為websocket隻有在建立連接配接時才用到了HTTP,更新完成之後就跟HTTP沒有任何關系了。

這就好像你喜歡的女生通過你要到了你大學室友的微信,然後他們自己就聊起來了。你能說這個女生是通過你去跟你室友溝通的嗎?不能。你跟HTTP一樣,都隻是個工具人。

為什麼有HTTP協定,還要有websocket協定

這就有點”借殼生蛋“的那意思。

為什麼有HTTP協定,還要有websocket協定

websocket的消息格式

上面提到在完成協定更新之後,兩端就會用webscoket的資料格式進行通信。

資料包在websocket中被叫做幀。

我們來看下它的資料格式長什麼樣子。

為什麼有HTTP協定,還要有websocket協定

這裡面字段很多,但我們隻需要關注下面這幾個。

opcode字段:這個是用來标志這是個什麼類型的資料幀。比如。

  • 等于1時是指text類型(string)的資料包
  • 等于2是二進制資料類型([]byte)的資料包
  • 等于8是關閉連接配接的信号

payload字段:存放的是我們真正想要傳輸的資料的長度,機關是位元組。比如你要發送的資料是字元串"111",那它的長度就是3。

為什麼有HTTP協定,還要有websocket協定

另外,可以看到,我們存放payload長度的字段有好幾個,我們既可以用最前面的7bit, 也可以用後面的7+16bit或7+64bit。

那麼問題就來了。

我們知道,在資料層面,大家都是01二進制流。我怎麼知道什麼情況下應該讀7bit,什麼情況下應該讀7+16bit呢?

websocket會用最開始的7bit做标志位。不管接下來的資料有多大,都先讀最先的7個bit,根據它的取值決定還要不要再讀個16bit或64bit。

  • 如果最開始的7bit的值是 0~125,那麼它就表示了 payload 全部長度,隻讀最開始的7個bit就完事了。
為什麼有HTTP協定,還要有websocket協定
  • 如果是126(0x7E)。那它表示payload的長度範圍在 126~65535 之間,接下來還需要再讀16bit。這16bit會包含payload的真實長度。
為什麼有HTTP協定,還要有websocket協定
  • 如果是127(0x7F)。那它表示payload的長度範圍>=65536,接下來還需要再讀64bit。這64bit會包含payload的長度。這能放2的64次方byte的資料,換算一下好多個TB,肯定夠用了。
為什麼有HTTP協定,還要有websocket協定

payload data字段:這裡存放的就是真正要傳輸的資料,在知道了上面的payload長度後,就可以根據這個值去截取對應的資料。

大家有沒有發現一個小細節,websocket的資料格式也是 資料頭(内含payload長度) + payload data 的形式。

為什麼有HTTP協定,還要有websocket協定

之前寫的《既然有HTTP協定,為什麼還要有RPC》提到過,TCP協定本身就是全雙工,但直接使用純裸TCP去傳輸資料,會有粘包的”問題”。為了解決這個問題,上層協定一般會用消息頭+消息體的格式去重新包裝要發的資料。

而消息頭裡一般含有消息體的長度,通過這個長度可以去截取真正的消息體。

HTTP協定和大部分RPC協定,以及我們今天介紹的websocket協定,都是這樣設計的。

為什麼有HTTP協定,還要有websocket協定

websocket的使用場景

websocket完美繼承了TCP協定的全雙工能力,并且還貼心的提供了解決粘包的方案。它适用于需要伺服器和用戶端(浏覽器)頻繁互動的大部分場景。比如網頁/小程式遊戲,網頁聊天室,以及一些類似飛書這樣的網頁協同辦公軟體。

回到文章開頭的問題,在使用websocket協定的網頁遊戲裡,怪物移動以及攻擊玩家的行為是伺服器邏輯産生的,對玩家産生的傷害等資料,都需要由伺服器主動發送給用戶端,用戶端獲得資料後展示對應的效果。

為什麼有HTTP協定,還要有websocket協定

總結

  • TCP協定本身是全雙工的,但我們最常用的HTTP1.1,雖然是基于TCP的協定,但它是半雙工的,對于大部分需要伺服器主動推送資料到用戶端的場景,都不太友好,是以我們需要使用支援全雙工的websocket協定。
  • 在HTTP1.1裡。隻要用戶端不問,服務端就不答。基于這樣的特點,對于登入頁面這樣的簡單場景,可以使用定時輪詢或者長輪詢的方式實作伺服器推送(comet)的效果。
  • 對于用戶端和服務端之間需要頻繁互動的複雜場景,比如網頁遊戲,都可以考慮使用websocket協定。
  • websocket和socket幾乎沒有任何關系,隻是叫法相似。
  • 正因為各個浏覽器都支援HTTP協定,是以websocket會先利用HTTP協定加上一些特殊的header頭進行握手更新操作,更新成功後就跟HTTP沒有任何關系了,之後就用websocket的資料格式進行收發資料。

最後

最近原創更文的閱讀量穩步下跌,思前想後,夜裡輾轉反側。

我有個不成熟的請求。

為什麼有HTTP協定,還要有websocket協定

離開廣東好長時間了,好久沒人叫我靓仔了。

大家可以在評論區裡,叫我一靓仔嗎?

我這麼善良質樸的願望,能被滿足嗎?

如果實在叫不出口的話,能幫我點下關注和右下角的點贊+在看嗎?

别說了,一起在知識的海洋裡嗆水吧

繼續閱讀