03.07 WebSocket是什麼原理?為什麼可以實現持久連接?

用戶68736146


WebSocket是一種不同於HTTP的協議,重要的是它完美彌補了http協議在某些場合下的重大不足。

接下來,我來簡單介紹下websocket與http的區別。

http/https請求是目前最廣泛使用的網絡通信協議,但是它們有一個非常大侷限性,那就是請求只能由agent發起,server只能被動的等待請求,而且一次請求就是一個response和request對應。雖然在HTTP 1.1中進行了改進,增加了keep-alive,出現了長連接這樣的概念,但是仍然是一個request對應一個response,這在request中攜帶大量header信息,而response中沒什麼有用信息的時候,無疑是對通信資源的浪費。

也許你覺得這麼說有點抽象,那麼我們舉個例子來說明,我們在瀏覽器上用QQ聊天,如果瀏覽器作為agent使用的是http協議與server端通信,那麼它需要定時去訪問server(輪詢),問它,喜歡的女神有沒有回覆我的信息啊。可是女神可能去洗澡了,手機沒帶進浴室,於是browser這個agent就不停的發一個大腦袋的request去server,每次拿回來的卻都是乾癟癟的response。這時候,如果你一邊看電影一邊等女神的回覆,那麼結果可能就是電影很卡,女神也沒有消息。

這個時候WebSocket協議就出現了。如果使用的是websocket協議,在登陸了網頁版QQ之後,這個瀏覽器就會作為agent向server發起請求,建立一個連接,在這個連接建立期間,是可以進行雙工通信的,就是說agent可以主動把消息發送給server,server也能在收到女神回覆後,第一時間把消息傳遞到你的屏幕上,減少了無意義的輪詢消耗,同時也保證了等女神回覆期間電影不卡,不會無聊。甚至也可以簡單的理解,只需要經過一次HTTP請求的連接建立,就可以進行源源不斷的信息傳送了。

說到實現長連接,說白了,websocket的設計者在最初就是沒想過像http協議那樣謹慎小氣,建立了連接說完一句話就跑。否則任何基於TCP的應用協議,都是可以進行長連接通信的。

也許正是當初http的如此設計,才給了WebSocket如今的用武之地。

以上是我的淺見,歡迎各位在下方評論區與我溝通交流。

我是蘇蘇思量,來自BAT的Java開發工程師,每日分享科技類見聞,歡迎關注我,與我共同進步。


一個存在感小透明


解釋WebSocket為什麼可以實現持久連接,還是先介紹一些什麼是WebSocket,以及它產生的原因是什麼。

是什麼WebSocket?

WebSocket是一個協議。

協議就是王八的屁股——規定,你可以不遵守,但是別人都遵守你不遵守,你就跟別人玩不到一塊去。

WebSocket協議在2008年誕生,2011年成為了國際標準,現在絕大部分瀏覽器都已經支持了。

產生的原因

其實原因是為了彌補HTTP協議的不足,因為HTTP協議只能由客戶端發起請求,並且一個Request要對應一個Response(長鏈接也是如此)。

舉個例子:

  • 我之前做過一個小項目,只有一個頁面,展示的是各個分公司當天的業績,就是掙了多少錢。後臺服務是Java,數據庫是Mysql,有一張彙總表,內容大概是北京-100萬,上海-80萬這樣的。


  • 流程很簡單,HTML頁面發起請求到Java,Java訪問數據庫查詢數據,再返回給HTML展示。但是Mysql中的彙總表的數據,是不定期更新的,可能10分鐘,可能20分鐘。

  • 最簡單的做法:HTML中用JS設置一個定時輪詢(Polling),每隔幾秒去發起一次請求,獲取最新的數據,如果數據沒有變化,頁面也保持變化;缺點很明顯,前端發起的很多請求都是無效的(因為數據沒有變化)。

WebSocket的通信原理

而WebSocket,是要在客戶端和服務器之間,建立一個通道,建立一個【真的長鏈接】。

WebSocket是要藉助於HTTP,完成一部分工作。我在找到一個WebSocket在線測試的網站,打開之後查看請求和響應(具體網站連接也看下面圖片中的信息)。

可以看出來協議裡面多了兩行:

Upgrade: websocket

Connection: Upgrade

這個就是關鍵內容了,通過請求告訴服務器:看清楚咯,請求要用WebSocket協議。


服務器會回答:好的,那我就切換到WebSocket協議啦。

到了這時候,HTTP完成它所有工作,客戶端和服務器已經建立好了一個通道,下面就按照WebSocket協議進行了,服務端也就可以主動推送信息給客戶端(雙向),並且這個連接會持續存在直到客戶端或者服務器端的某一方主動的關閉連接,故此WebSocket也就實現了持久連接。


希望我的回答,能夠幫助到你!我將持續分享Java開發、架構設計、程序員職業發展等方面的見解,希望能得到你的關注。


會點代碼的大叔


對於Web項目開發,往往需要前端和後端進行數據通信交互,在以前如果前端要和後端通信往往是通過Ajax這種異步方式,但這樣就存在一些弊端,比如說實時性要求高的項目(如在線聊天室)就不好使用Ajax這種方式,而應該使用“服務器推”技術。

在WebSocket出來之前,實現“服務器推”基本上都是野路子,不夠優雅,而WebSocket一經推出,就可以便捷實現長連接了。

WebSocket是一種新的協議

WebSocket是一種借鑑了HTTP協議的新協議,隨著HTML5一起推出了WebSocket API(WebSocket不屬於HTML5,HTML5中所謂的“WebSocket”其實是指“WebSocket API”)。WebSocket和HTTP沒有必然聯繫,不能單純的把WebSocket理解成HTTP協議的升級,不過WebSocket為了兼容現有的瀏覽器握手規範,借鑑了HTTP協議規範!不過WebSocket協議解決了HTTP協議的被動性,可以實現長連接。

WebSocket為什麼可以持久化連接?

WebSocket協議實現了瀏覽器與服務器的全雙工通信,它通過已建立的TCP連接來傳輸數據,WebSocket協議的特點是:

  • WebSocket協議名為“ws”,它有對應的安全連接協議名為“wss”;

  • 先像TCP一樣建立連接,WebSocket基於TCP協議;

  • 客戶端和服務端握手過程中,客戶端會發送一個包含 Upgrade:請求頭的HTTP請求,告訴服務器建立一個WebSocket連接;

  • 服務端收到請求後,會將協議轉換為WebSocket協議,且在協議轉換過程中該連接沒有中斷;

  • 當第一個HTTP Request請求建立TCP連接之後,以後的數據交換就不需要再次發送HTTP Request了,因此這個連接也就變成了長連接;

  • 不同URI可複用同一個WebSocket連接。

以上就是我的觀點,對於這個問題大家是怎麼看待的呢?歡迎在下方評論區交流 ~ 我是科技領域創作者,十年互聯網從業經驗,歡迎關注我瞭解更多科技知識!

網絡圈


先說為什麼WebSocket可以實現持久連接。這個前面已經有人回答了:WebSocket是基於TCP的,TCP本身就支持長連接。

那麼問題就變成了,為什麼早期的HTTP不支持長連接?以及HTTP1.1開始默認支持長連接了,為什麼還要WebSocket?


為什麼早期的HTTP不支持長連接?

個人認為有幾個方面的原因:

  • 網絡環境:早期網絡,帶寬不高,速度也不咋的,長連接浪費資源

  • 交互方式:早期的網頁以瀏覽為主,交互性不強,可能頁面展示出來後,閱讀需要花較長的時間,保持長連接意義不大。Web2.0開始,交互性越來越強,頁面展示後,可能還會有各種交互。
  • 底層IO實現:epoll是2003年,在linux的2.6版本的內核中加入的。在此之前的select和poll都有一個問題,都是通過遍歷文件描述符來獲取已經就緒的socket的,隨著連接數的增加,性能會越來越低。


HTTP1.1開始默認支持長連接了,為什麼還要WebSocket?

要回答這個問題,需要先來看HTTP的設計目的是什麼。可以參考Roy Thomas Fielding博士的博士論文《Architectural Styles and the Design of Network-based Software Architectures》,也就是傳說中的REST,這是HTTP的設計指導原則。

Roy博士認為一個良好設計的 Web 應用應該這樣運轉:一個由網頁組成的網絡(一個虛擬狀態機),用戶通過選擇鏈接在應用中前進(狀態遷移),導致下一個頁面(應用的下一個狀態的表述)被轉移給用戶,並且呈現給他們,以便他們來使用。

可以看出普通Web應用是個標準的CS架構,且通過Client來操作Server的「狀態變遷」!Server只對Client的請求做出響應,而不會主動的響應Client。

也就是說HTTP是「單向的」!但實際場景中還是有Server向Client推送消息的情況,所以就有了類似ajax輪詢,Comet這樣的方式。但實際還是需要Client主動發送請求,Server響應,只是響應的時機不同。

WebSocket就是為了解決這個問題的。怎麼解決的呢?定義了一套新的協議!只不過以HTTP做了個嫁衣。


WebSocket原理

最後來說說WebSocket是如何實現的!

協議傳輸方式其實都是大同小異,可以回想一下編寫Socket的代碼邏輯:

  • 建立連接

  • Client組裝請求協議消息發送
  • Server接收協議消息,進行解析處理
  • 然後再組裝響應協議消息,發送回去

Server端直接向Client推送消息的流程也是類似的。區別就是Client與Server是「一對多」的關係。也就是說Server端有多個連接。

那要Server端能夠推送消息給指定Client端,無非就是Server端需要維護這個連接池。你可以把這個連接池想象成一個key,value鍵值對。key是ClientId,key是Connection。只需要根據ClientId找到對應的Connection,實際發送方式和Client是一樣的。


WebSocket有一個比較特別的地方就是,它是通過HTTP來建立連接的:

  • Client首先通過HTTP發送一個GET請求來告訴Server,我們能通過WebSocket來進行通信嗎?

  • Server答覆101,表示好的
  • 然後Client和Server就開始使用WebSocket開始通信了

這個流程是不是似曾相識:

  • 你在fb上撩到個「外國妹子」,你問道:Can you speak Chinese?

  • 對方回答:Yes

  • 然後你們就開始用中文撩了~「大妹子,你哪人啊?」「東北的」「這麼巧,俺也是」......


架構思維


首先需要明白:基於TCP的應用層協議,只要設計者願意,都是可以實現持久連接的。

你問的方式,大概是在和HTTP做比較。

HTTP

http協議是請求應答式的文本協議,協議設計就是Client-Server模式,出發點是服務端為客戶端提供資源。http服務端只能監聽和響應來自客戶端的請求,http客戶端只能發起請求接受響應,這個是HTTP協議本身的設計,雙向通信不在設計的考慮之內。

關於Http協議,額外說點:

HTTP1.0/0.9

不支持keep-alive,要完成一次HTTP請求,需要建立一個新的TCP連接,然後發送http請求,待接收響應後關閉連接。

HTTP1.1

默認使用keep-alive,一次HTTP請求完成後不會關閉TCP連接,會繼續為下一個HTTP請求服務(可以類比數據庫連接池和線程池的設計),減小建立和關閉TCP連接的開銷(三次握手四次揮手)。當然閒置超時後也會關閉。並非樓下所說的“把多個HTTP請求合併為一個”。

HTTP協議的設計無法實現對TCP通道的分用和複用。因為HTTP協議沒有請求的唯一標記(僅僅是URL是不行的,原因大家想)用來從同一TCP通道分離不同的HTTP消息,所以一個完整的HTTP請求在發送請求到響應回來之間是獨佔一個TCP通道的!是不是覺得HTTP對TCP的利用率太低了?而關於pipeline模式,不管在服務端還是客戶端排隊,HTTP響應依然要通過進入服務端隊列的順序返回,這樣才能和客戶端HTTP請求隊列用順序做對應!所以pipeline模式某個請求被服務端因為某些原因阻塞了的情況下,後續請求都會阻塞,會引起很大的問題,實際上很少用。

瀏覽器或者一般HTTP客戶端組件為某一個服務器端點(域名+端口)保留4-6條活躍TCP連接。你可以F12觀察瀏覽器,看看同時是幾個請求阻塞了就知道你的瀏覽器設置的多少。比較大的門戶網站,比如京東,首頁請求非常多,但是大量都需要排隊等TCP空閒。限制客戶端的連接數量的出發點主要是性能,否則會佔用服務器太多Socket資源(考慮socket預留的讀寫緩衝區,windows的內核對象或者linux的文件句柄)或者變相地造成DoS攻擊。

Tips:HTTP客戶端組件一般會提供諸如ConnectionLimit的選項讓你控制最大TCP連接數。如果你是桌面客戶端,或者請求遠程服務,不宜設置過大。如果你是內部服務之間調用,可以根據需求合理設置以增加併發性能。

HTTP2.0

針對以上的問題(主要是性能)做了很多改進,這個也會提高很多人在後端不同服務器之間做通信時選擇HTTP(我在HTTP2.0出來之前就是自己設計RPC方案)。詳細的HTTP2.0的東西,這裡不展開了,詳細參考官方文檔。

HTTP相關知識推薦《HTTP權威指南》以及相關的RFC文檔,儘量少去看博客上面支離破碎的小知識,體系化的認知結構對你幫助更大。

WebSocket

WebSocket的出現,就是為了解決http協議不支持雙向通信的缺口。所以WebSocket的握手協議就是使用的HTTP消息來Upgrade。

現代的Web場景,服務端推送的需求非常大,這個發展過程中使用的Ajax輪詢,Comet等都只是臨時解決方案,從設計上看,只為滿足需求,一點都不優雅。

Html5規範將WebSocket納入後,得到了現代幾乎所有瀏覽器的支持,當然IE(10+才支持)仍然是一個巨坑,在乎用戶覆蓋面的產品依然要通過瀏覽器是否支持ws來做出降級處理(輪詢、長連接)。

websocket協議實現獨佔一條tcp通道,它負責從tcp流確定消息邊界,解析出每個獨立的消息包。可進行全雙工的雙向通信。題主所謂的WebSocket可以實現持久連接,只是的一個服務端WebSocket會話和對應的客戶端WebSocket會話在使用一個固定的保持連接的TCP通信而已。一般需要將服務端WebSocket會話和某位用戶關聯起來(客戶單連接後,可以再單獨發送憑證驗證),實現給某個用戶推送消息,只需根據關聯找到對應的WebSocket會話調用發送API即可。

應用

使用單獨實現websocket協議的服務\\客戶端組件,可以更加輕鬆地實現自定義協議:在websocket的二進制或者文本消息體內或者直接使用websocket的自協議定義機制封裝自己定義的協議。

推薦大家如果有些需要自建IM服務器,推送服務器的場合嘗試先用WebSocket來實現。負載高(協議頭消耗小),協議簡潔,幾乎所有客戶端(減少了大量的工作)都有對應的開源項目可用,同時還是唯一可以在瀏覽器上用的雙向通信協議(flash和silverlight等插件方式除外)。

如果你要用websocket實現請求應答式的子協議,要點是你要設計唯一的請求標誌,響應也將請求標誌帶回來,然後你就可以從客戶端的請求隊列中查找響應對應的請求將響應交給上層處理!

特別注意:

關於webcket持久連接,本質上是下層tcp連接的保持,核心問題同樣是如何保活。需要考慮Nat失效(基站最突出,一般有效期只有3分鐘)或者其它網絡原因導致大量半連接存在。解決方案就是合理的心跳時間,一般我設置為2分50秒的樣子。

其它

不論是否從事網絡編程,都應該花時間學習下TCP/IP協議簇方面的知識,著重理解分層原理,各層的功能和為上層提供了哪些功能。就像這個問題,如果不對TCP有所瞭解,回答的內容就沒多大意義了。閱讀一個你比較熟悉的語言的的一種協議(比如http)實現項目的源碼,幫助應該很大。

和網絡IO密切相關的就是線程,要設計高可用的TCP服務器,必須要熟悉多線程。網絡IO和多線程是我認為最重要的兩個核心知識點。

關於協議的設計,你可以多學習其他優秀的基於TCP實現的應用層協議,簡單的就有Redis的通信協議,裡面有阻塞式的消費者隊列,那個就需要一條單獨的tcp通道。協議設計是很有意思的一件事情,就是mysql和mongodb的通信協議我也不會放過,去看看,會給自己設計協議帶來不少的參考價值。

如果時間允許,有標準的協議最好看看RFC文檔,現在Chrome的翻譯已經很好了,如果英文不太好,問題也不大。

關於TCP/IP相關的書籍

《計算機網絡:自頂向下方法》和謝希仁的《計算機網絡》都是不錯的入門書籍。

《TCP/IP詳解》是經典,雖然出版已久,內容是沒過時的。

網絡應用脫離不了操作系統,所以可以再看看操作系統關於網絡IO這一塊的設計。

實際開發更多和Socket以及多線程打交道,Windows下面可以看看《Windows核心編程》。

其它的就是開源項目:Nginx,netty等大量優秀的項目都在等你。

還是要感謝大家對我寫的東西有那麼一點感興趣,能對大家有所幫助就更好了。


遷徙de麻雀


TCP長連接,只要連接不釋放,Server端可以主動向Client推數據。


光明右使8787


http請求響應模式是一夜情,websocket請求響應模式是小三,tcp請求響應模式是夫妻關係。

一句話就可以說清楚的問題,饒了這麼大的一個圈子。因為websocket的本質還是基於tcp的,tcp是老爹,既然tcp可以全雙工,那麼websocket當然可以實現。http不支持是因為協議本身規定是隻能響應一次,第二次服務器不認識客戶端了 就像一夜情。如何解決這個問題?原理就是讓服務器保留客戶端的身份信息,你不需要再一次告訴服務器,我是誰,我是你的情人,可以再上車。


分享到:


相關文章: