WebSocket是什么原理?
首先我們看一下websocket的出現(xiàn)背景,我們知道http系列協(xié)議是建立在tcp上的,理論上,他是可以可以雙向通信的。但是http1.1之前,服務(wù)器沒(méi)有實(shí)現(xiàn)推送的功能。每次都是客戶(hù)端請(qǐng)求,服務(wù)器響應(yīng)。下面看一下http協(xié)議關(guān)于請(qǐng)求處理的發(fā)展。
http1.0的時(shí)候,一個(gè)http請(qǐng)求的生命周期是客戶(hù)端發(fā)起請(qǐng)求,服務(wù)器響應(yīng),斷開(kāi)連接。但是我們知道tcp協(xié)議的缺點(diǎn)就是,三次握手需要時(shí)間,再加上慢開(kāi)始等特性,每一個(gè)http請(qǐng)求都這樣的話(huà),效率就很低。http1.1的時(shí)候,默認(rèn)開(kāi)啟了長(zhǎng)連接(客戶(hù)端請(qǐng)求中設(shè)置了keep-alive頭),服務(wù)器處理一個(gè)請(qǐng)求后,不會(huì)立刻關(guān)閉連接,而是會(huì)等待一定的時(shí)間。如果沒(méi)有請(qǐng)求才關(guān)閉連接。這樣瀏覽器不僅可以在一個(gè)tcp連接中,不斷地發(fā)送請(qǐng)求(服務(wù)器也會(huì)限制一個(gè)連接上可以處理的請(qǐng)求閾值),甚至可以一次發(fā)很多個(gè)請(qǐng)求。這就是http1.1的管道化(pipeline)技術(shù)。但是他也有個(gè)問(wèn)題,因?yàn)閷?duì)于基于http協(xié)議的客戶(hù)端來(lái)說(shuō),雖然他可以發(fā)很多請(qǐng)求出去,但是當(dāng)一個(gè)請(qǐng)求對(duì)于的回包回來(lái)時(shí),他卻無(wú)法分辨是屬于哪個(gè)請(qǐng)求的。所以回包只能按請(qǐng)求順序返回,這就引來(lái)了另一個(gè)問(wèn)題-線(xiàn)頭阻塞(Head-of-Link Blocking)。并且http1.1雖然支持長(zhǎng)連接,但是他不支持服務(wù)端推送(push)的能力。如果服務(wù)器有數(shù)據(jù)要給客戶(hù)端,也只能等著客戶(hù)端來(lái)取(pull)。來(lái)到了http2.0,不僅實(shí)現(xiàn)了服務(wù)器推送,還使用了幀(iframe),流(stream)等技術(shù)解決了線(xiàn)頭阻塞的問(wèn)題,http2.0在一個(gè)tcp連接中,可以同時(shí)發(fā)送多個(gè)http請(qǐng)求,每個(gè)請(qǐng)求是一個(gè)流,一個(gè)流可以分成很多幀,有了標(biāo)記號(hào),服務(wù)器可以隨便發(fā)送回包,客戶(hù)端收到后,根據(jù)標(biāo)記,重新組裝就可以。以上是http協(xié)議的關(guān)于請(qǐng)求的一些發(fā)展,而websocket就服務(wù)端推送提供了另外一種解決方案。他本質(zhì)上是在tcp協(xié)議上封裝的另一種應(yīng)用層協(xié)議(websocket協(xié)議)。因?yàn)樗腔趖cp的,所以服務(wù)端推送自然不是什么難題。但是在實(shí)現(xiàn)上,他并不是直接連接一個(gè)tcp連接,然后在上面?zhèn)鬏敾趙ebsocket協(xié)議的數(shù)據(jù)包。他涉及到一個(gè)協(xié)議升級(jí)(交換)的過(guò)程。我們看看這個(gè)過(guò)程。1 客戶(hù)端發(fā)送協(xié)議升級(jí)的請(qǐng)求。在http請(qǐng)求加上下面的http頭2 服務(wù)器如果支持websocket協(xié)議的話(huà),會(huì)返回101狀態(tài)碼表示同意協(xié)議升級(jí),并且支持各種配置(如果服務(wù)器不支持某些功能或版本,或告訴客戶(hù)端,客戶(hù)端可以再次發(fā)送協(xié)議升級(jí)的請(qǐng)求)。服務(wù)會(huì)返回形如下面的http頭(可以參考websocket協(xié)議)。3 這樣就完成了協(xié)議的升級(jí),后續(xù)的數(shù)據(jù)通信,就是基于tcp連接之上,使用websocket協(xié)議封裝的數(shù)據(jù)包。下面我們通過(guò)wireshark來(lái)了解這個(gè)過(guò)程。首先我們啟動(dòng)一個(gè)服務(wù)器(ip:192.168.8.226)。我們可以直接在瀏覽器控制臺(tái)進(jìn)行測(cè)試這時(shí)候,我們看看wireshark的包。首先看前面三條記錄,這是tcp三次握手的數(shù)據(jù)包。這個(gè)我們都了解了,就不展示。接著看第四條記錄。展開(kāi)后如下。我們看到建立tcp連接后,瀏覽器發(fā)了一個(gè)http請(qǐng)求,并帶上了幾個(gè)websocket的數(shù)據(jù)包。接著看下面的一條。服務(wù)返回了同意升級(jí)協(xié)議或者說(shuō)交換協(xié)議。從服務(wù)器代碼中我們看到,在建立連接的時(shí)候我們給瀏覽器推送了一個(gè)get it的字符串。繼續(xù)看上面的記錄。這就是服務(wù)器給瀏覽器推送的基于websocket協(xié)議的數(shù)據(jù)包。具體每個(gè)字段什么意思,參考websocket協(xié)議就可以。繼續(xù)往下看一條記錄是針對(duì)服務(wù)器推送的數(shù)據(jù)包的一個(gè)tcp的ack。最后我們可以順便看看最后三條寫(xiě)著keep-alive的記錄。這就是之前文章里講到的tcp層的keep-alive。因?yàn)槲覀円恢睕](méi)有數(shù)據(jù)傳輸,所以tcp層會(huì)間歇性地發(fā)送探測(cè)包。我們可以看看探測(cè)包的結(jié)構(gòu)。有一個(gè)字節(jié)的探測(cè)數(shù)據(jù)。如果這時(shí)候我們發(fā)送一個(gè)數(shù)據(jù)包給服務(wù)器,又是怎樣的呢。白色背景的三條數(shù)據(jù),分別是瀏覽器發(fā)送給服務(wù)器的數(shù)據(jù),服務(wù)器推送回來(lái)的數(shù)據(jù)。tcp的ack。我們發(fā)現(xiàn),服務(wù)器給瀏覽器推送的時(shí)候,瀏覽器會(huì)發(fā)送ack,但是瀏覽器給服務(wù)器發(fā)送的時(shí)候,服務(wù)器貌似沒(méi)有返回ack。下面我們看看為什么。首先我們看瀏覽器發(fā)出的包。再看看服務(wù)器給瀏覽器推送的數(shù)據(jù)包。我們發(fā)現(xiàn)服務(wù)器(tcp)推送消息的時(shí)候把a(bǔ)ck也帶上了。而不是發(fā)送兩個(gè)tcp包。這就是tcp的機(jī)制。tcp不會(huì)對(duì)每個(gè)包都發(fā)ack,他會(huì)累積確認(rèn)(發(fā)ack),以減少網(wǎng)絡(luò)的包,但是他也需要保證盡快地回復(fù)ack,否則就會(huì)導(dǎo)致客戶(hù)端觸發(fā)超時(shí)重傳。tcp什么時(shí)候發(fā)送確認(rèn)呢?比如需要發(fā)送數(shù)據(jù)的時(shí)候,或者超過(guò)一定時(shí)間沒(méi)有收到數(shù)據(jù)包,或者累積的確認(rèn)數(shù)量達(dá)到閾值等。既然研究了tcp,我們不妨多研究點(diǎn),我們看一下,如果這時(shí)候關(guān)閉服務(wù)器會(huì)怎樣。服務(wù)器會(huì)發(fā)送一個(gè)重置包給瀏覽器,告訴他需要斷開(kāi)連接。繼續(xù),如果是瀏覽器自己調(diào)用close去關(guān)閉連接會(huì)怎樣。我們看到websocket首先會(huì)發(fā)送一個(gè)FIN包給服務(wù)器,然后服務(wù)器也會(huì)返回一個(gè)FIN包,然后才開(kāi)始真正的四次揮手過(guò)程。并且四次揮手的第一個(gè)fin包是服務(wù)器發(fā)的。我們?cè)賮?lái)看看安全版本的websocket。我們啟動(dòng)一個(gè)https服務(wù)器。然后在瀏覽器控制臺(tái)執(zhí)行。然后來(lái)看看wireshark。首先建立tcp連接,然后建立tls連接。后續(xù)的數(shù)據(jù)通信就可以基于加密來(lái)進(jìn)行了。不再重復(fù)。后續(xù)分析tls協(xié)議的時(shí)候再分析。經(jīng)過(guò)一系列的分析,我們對(duì)websocket協(xié)議應(yīng)該有了更多的了解,最后再說(shuō)一個(gè)關(guān)于websocket的點(diǎn)。我們發(fā)現(xiàn)如果在websocket連接上,一直不通信的話(huà),websocket連接所維持的時(shí)間是依賴(lài)tcp實(shí)現(xiàn)的。因?yàn)槲覀儼l(fā)現(xiàn)tcp層會(huì)一直發(fā)送探測(cè)包。達(dá)到閾值之后,連接就會(huì)被斷開(kāi)。所以我們想維持websocket連接的話(huà),需要自己去發(fā)送心跳包,比如ping,pong。總結(jié):本文分析了websocket的基本原理,但不涉及協(xié)議的內(nèi)容,如需了解協(xié)議的內(nèi)容,可以參考rfc文檔。