之前自己一個人負責完成了公司的訊息推送服務,和移動端配合完成了掃碼登入、訂單訊息推送、活動訊息廣播等功能。為了加深自己對Websocket協議的理解,自己通過進行抓包的方式學習了一番。現在分享出來,希望對大家能有所幫助。
Chrome控制檯
(1)F12進入控制檯,點選Network,選中ws欄,注意選中Filter。
(2)重新整理頁面會得到一個ws連結。
(3)點選連結可以檢視連結詳情
注意紅框標出的資訊,後面會詳細說明。 (4)當然也可以切換到Frames檢視發出和接收的訊息,但是非常的簡陋,只能看到訊息內容,資料長度和時間
Fiddler:抓包除錯利器
(1)開啟Fiddler,點開選單欄的Rules,選擇Customize Rules...
(2)這時會開啟CustomRules.js檔案,在class Handlers中加入以下程式碼
static function OnWebSocketMessage(oMsg: WebSocketMessage) {
// Log Message to the LOG tab
FiddlerApplication.Log.LogString(oMsg.ToString());
}
複製程式碼
(3)儲存後就可以在Fiddler右邊欄的Log標籤裡,看到WebSocket的資料包 下列圖中紅框標出的Client.1代表客戶端發出的第一條訊息;對應的Server.1代表服務端發出的第一條訊息。MessageType:Text代表正常的通話訊息;Close代表會話關閉。 客戶端發出的訊息:
服務端發出的訊息:
然後我們會發現每次會話關閉都是由客戶端發起的:
相對於Chrome控制檯來說Fiddler抓包更加詳細一些,能知道會話訊息是由客戶端還是服務端發出並且能知道訊息型別。但是這仍然滿足不了深入理解學習Websocket協議的目的。如果是處理HTTP、HTTPS,還是用Fiddler。其他協議比如TCP,UDP 就用WireShark。TPC/IP協議是傳輸層協議,主要解決資料如何在網路中傳輸,而HTTP、Websocket是應用層協議,主要解決如何包裝資料。因為應用層是在傳輸層的基礎上包裝資料,所以我們還是從底層開始瞭解Websocket到底是個啥?是如何工作的?
WireShark
WireShark(前稱Ethereal)是一個網路封包分析軟體。網路封包分析軟體的功能是擷取網路封包,並儘可能顯示出最為詳細的網路封包資料。WireShark抓包是根據TCP/IP五層協議來的,也就是物理層、資料鏈路層、網路層、傳輸層、應用層。我們主要關注傳輸層和應用層。
TCP三次握手
我們都知道,TCP建立連線時,會有三次握手過程。下圖是WireShark截獲到的三次握手的三個資料包(雖然叫資料包,但是三次握手包是沒有資料的)。
點選上圖中的資料包就可以檢視每個資料包的詳情,這裡我們需要明確幾個概念才能看懂每個資料包代表啥意義: SYN:同步位元,建立連線。 ACK:確認位元,置1表示這是一個確認的TCP包,0則不是。 PSH:推送位元,當傳送端PSH=1時,接收端應儘快交付給應用程式。
- 第一次握手
可以看到我們開啟的Transmission Control Protocol即為傳輸層(Tcp) SYN置為1,客戶端向服務端傳送連線請求包。
- 第二次握手
伺服器收到客戶端發過來的TCP報文,由SYN=1知道客戶端要求建立聯機,向客戶端傳送一個SYN=1,ACK=1的TCP報文,將確認序號設定為客戶端的序列號加1。
- 第三次握手
客戶端接收到伺服器發過來的包後檢查確認序列號是否正確,即第一次傳送的序號+1,以及標誌位ACK是否為1。若正確則再次傳送確認包,ACK標誌為1。連結建立成功,可以傳送資料了。
一次特殊的HTTP請求
緊接著是一次Http請求(第四個包),說明Http的確是使用Tcp建立連線的。
先來看傳輸層(Tcp): PSH(推送位元)置1,ACK置1,PSH置1說明開始傳送資料,同時傳送資料ACK要置1,因為需要接收到這個資料包的端給予確認。PSH為1的情況,一般只出現在 DATA內容不為0的包中,也就是說PSH為1表示的是有真正的TCP資料包內容被傳遞。
再來看應用層(Http):這是一次特殊的Http請求,為什麼是一次特殊的Http請求呢?Http請求頭中Connection:Upgrade Upgrade:websocket,Upgrade代表升級到較新的Http協議或者切換到不同的協議。很明顯WebSocket使用此機制以相容的方式與HTTP伺服器建立連線。WebSocket協議有兩個部分:握手建立升級後的連線,然後進行實際的資料傳輸。首先,客戶端通過使用Upgrade: WebSocket和Connection: Upgrade頭部以及一些特定於協議的頭來請求WebSocket連線,以建立正在使用的版本並設定握手。伺服器,如果它支援協議,回覆與相同Upgrade: WebSocket和Connection: Upgrade標題,並完成握手。握手完成後,資料傳輸開始。這些資訊在前面的Chrome控制檯中也可以看到。
請求:
響應: 響應狀態碼 101 表示伺服器已經理解了客戶端的請求,在傳送完這個響應後,伺服器將會切換到在Upgrade請求頭中定義的那些協議。
由此我們可以總結出: Websocket協議本質上是一個基於TCP的協議。建立連線需要握手,客戶端(瀏覽器)首先向伺服器(web server)發起一條特殊的http請求,web server解析後生成應答到瀏覽器,這樣子一個websocket連線就建立了,直到某一方關閉連線。
Websocket的世界
通訊協議格式是WebSocket格式,伺服器端採用Tcp Socket方式接收資料,進行解析,協議格式如下:
首先我們需要知道資料在物理層,資料鏈路層是以二進位制進行傳遞的,而在應用層是以16進位制位元組流進行傳輸的。
第一個位元組:
FIN:1位,用於描述訊息是否結束,如果為1則該訊息為訊息尾部,如果為零則還有後續資料包; RSV1,RSV2,RSV3:各1位,用於擴充套件定義的,如果沒有擴充套件約定的情況則必須為0 OPCODE:4位,用於表示訊息接收型別,如果接收到未知的opcode,接收端必須關閉連線。
Webdocket資料幀中OPCODE定義: 0x0表示附加資料幀 0x1表示文字資料幀 0x2表示二進位制資料幀 0x3-7暫時無定義,為以後的非控制幀保留 0x8表示連線關閉 0x9表示ping 0xA表示pong 0xB-F暫時無定義,為以後的控制幀保留
第二個位元組:
MASK:1位,用於標識PayloadData是否經過掩碼處理,客戶端發出的資料幀需要進行掩碼處理,所以此位是1。資料需要解碼。 PayloadData的長度:7位,7+16位,7+64位 如果其值在0-125,則是payload的真實長度。 如果值是126,則後面2個位元組形成的16位無符號整型數的值是payload的真實長度。 如果值是127,則後面8個位元組形成的64位無符號整型數的值是payload的真實長度。
上圖是客戶端傳送給服務端的資料包,其中PayloadData的長度為二進位制:01111110——>十進位制:126;如果值是126,則後面2個位元組形成的16位無符號整型數的值是payload的真實長度。也就是圈紅的十六進位制:00C1——>十進位制:193 byte。所以PayloadData的真實資料長度是193 bytes;
根據我們的分析,客戶端到服務端資料包的websocket幀圖應該為:
我們再來抓包分析一下伺服器到客戶端的資料包:
可以發現伺服器傳送給客戶端的資料包中第二個位元組中MASK位為0,這說明伺服器傳送的資料幀未經過掩碼處理,這個我們從客戶端和服務端的資料包截圖中也可以發現,客戶端的資料被加密處理,而服務端的資料則沒有。(如果伺服器收到客戶端傳送的未經掩碼處理的資料包,則會自動斷開連線;反之,如果客戶端收到了服務端傳送的經過掩碼處理的資料包,也會自動斷開連線)。
掩碼處理:
未掩碼處理:
根據我們的分析,服務端到客戶端資料包的websocket幀圖應該為:
TCP KeepAlive
如上圖所示,TCP保活報文總是成對出現,包括TCP保活探測報文和TCP保活探測確認報文。 TCP保活探測報文是將之前TCP報文的確認序列號減1,並設定1個位元組,內容為“00”的應用層資料,如下圖所示:
TCP保活探測確認報文就是對保活探測報文的確認,其報文格式如下:
因為Websocket通過Tcp Socket方式工作,現在考慮一個問題,在一次長連線中,伺服器怎麼知道訊息的順序呢?這就涉及到tcp的序列號(Sequence Number)和確認號(Acknowledgment Number)。我的理解是序列號是傳送的資料長度;確認號是接收的資料長度。這樣講比較抽象,我們從TCP三次握手開始(結合下圖)詳細分析一下。
包1: TCP會話的每一端的序列號都從0開始,同樣的,確認號也從0開始,因為此時通話還未開始,沒有通話的另一端需要確認
包2: 服務端響應客戶端的請求,響應中附帶序列號0(由於這是服務端在該次TCP會話中傳送的第一個包,所以序列號為0)和相對確認號1(表明服務端收到了客戶端傳送的包1中的SYN)。需要注意的是,儘管客戶端沒有傳送任何有效資料,確認號還是被加1,這是因為接收的包中包含SYN或FIN標誌位。
包3: 和包2中一樣,客戶端使用確認號1響應服務端的序列號0,同時響應中也包含了客戶端自己的序列號(由於服務端傳送的包中確認收到了客戶端傳送的SYN,故客戶端的序列號由0變為1)此時,通訊的兩端的序列號都為1。
包4:客戶端——>伺服器 這是流中第一個攜帶有效資料的包(確切的說,是客戶端傳送的HTTP請求),序列號依然為1,因為到上個包為止,還沒有傳送任何資料,確認號也保持1不變,因為客戶端沒有從服務端接收到任何資料。需要注意的是,包中有效資料的長度為505位元組
包5:伺服器——>客戶端 當上層處理HTTP請求時,服務端傳送該包來確認客戶端在包4中發來的資料,需要注意的是,確認號的值增加了505(505是包4中有效資料長度),變為506,簡單來說,服務端以此來告知客戶端端,目前為止,我總共收到了506位元組的資料,服務端的序列號保持為1不變。
包6:伺服器——>客戶端 這個包標誌著服務端返回HTTP響應的開始,序列號依然為1,因為服務端在該包之前返回的包中都不帶有有效資料,該包帶有129位元組的有效資料。
包7: 由於上個資料包的傳送,TCP客戶端的確認序列號增長至130,從服務端接收了129位元組的資料,客戶端的確認號由1增長至130 理解了序列號和確認序列號是怎麼工作的之後,我們也就知道“TCP保活探測報文是將之前TCP報文的確認序列號減1,並設定1個位元組”為什麼要這麼搞了。減一再加一,是為了保證一次連線中keep alive不影響序列號和確認序列號。Keep alive 中的1byte 00的資料並不是真正要傳遞的資料,而是tcp keep alive約定俗稱的規則。
總結: WebSocket 是一個獨立的基於 TCP 的協議,它與 HTTP 之間的唯一關係就是它的握手請求可以作為一個升級請求(Upgrade request)經由 HTTP 伺服器解釋。再嚴謹一點:WebSocket是一個網路通訊協議, 只要理解上面的資料幀格式和握手流程, 都可以完成基於websokect的即時通訊。
微信公眾號:關注公眾號,獲取最新更新