之前自己一個人負責完成了公司的消息推送服務,和移動端配合完成了掃碼登錄、訂單消息推送、活動消息廣播等功能。為了加深自己對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的即時通訊。