云享專家韋世東:開發(fā)者必知必會(huì)的 WebSocket 協(xié)議

      網(wǎng)友投稿 907 2025-03-31

      關(guān)于 WebSocket,我之前也寫過了兩篇文章進(jìn)行介紹:《WebSocket 從入門到寫出開源庫》和《Python如何爬取實(shí)時(shí)變化的WebSocket數(shù)據(jù)》。今天這篇文章,大體上與之前的文章內(nèi)容結(jié)構(gòu)相似。但質(zhì)量更進(jìn)一步,適合想要完全掌握 WebSocket 協(xié)議的朋友,因此特來掘金分享給大家。

      WebSocket 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通信的協(xié)議,它的出現(xiàn)使客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單。WebSocket 通常被應(yīng)用在實(shí)時(shí)性要求較高的場(chǎng)景,例如賽事數(shù)據(jù)、股票證券、網(wǎng)頁聊天和在線繪圖等。

      在本篇文章中,你將收獲如下知識(shí):

      讀懂 WebSocket 協(xié)議規(guī)范文檔 RFC6455

      WebSocket 與 HTTP 的關(guān)系

      數(shù)據(jù)幀格式及字段含義

      客戶端與服務(wù)端交互流程

      客戶端與服務(wù)端如何保持連接

      何時(shí)斷開連接

      本篇文章適用于互聯(lián)網(wǎng)領(lǐng)域的開發(fā)者和產(chǎn)品經(jīng)理

      開始

      WebSocket 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通信的協(xié)議。WebSocket 通信協(xié)議于 2011 年被 IETF 定為標(biāo)準(zhǔn) RFC6455,并由 RFC7936 補(bǔ)充規(guī)范。看到這里,很多讀者會(huì)有疑問:什么是 RFC?

      RFC 是一系列以編號(hào)排定的文件,它由一系列草案和標(biāo)準(zhǔn)組成。幾乎所有互聯(lián)網(wǎng)通信協(xié)議均記錄在 RFC 中,例如 HTTP 協(xié)議標(biāo)準(zhǔn)、本篇介紹的 WebSocket 協(xié)議標(biāo)準(zhǔn)、Base64 編碼規(guī)范等。除此之外,RFC 還加入了許多論題。在本篇 Chat 中,我們對(duì) WebSocekt 的學(xué)習(xí)和討論將基于 RFC6455。

      WebSocket 協(xié)議的來源

      在 WebSocket 協(xié)議出現(xiàn)以前,網(wǎng)站通常使用輪詢來實(shí)現(xiàn)類似“數(shù)據(jù)實(shí)時(shí)更新”這樣的效果。要注意的是,這里的“數(shù)據(jù)實(shí)時(shí)更新”是帶有引號(hào)的,這表示并不是真正意義上的數(shù)據(jù)實(shí)時(shí)更新。輪詢指的是在特定的時(shí)間間隔內(nèi),由客戶端主動(dòng)向服務(wù)端發(fā)起 HTTP 請(qǐng)求,以確認(rèn)是否有新數(shù)據(jù)的行為。下圖描述了輪詢的過程:

      首先,客戶端會(huì)向服務(wù)端發(fā)出一個(gè) HTTP 請(qǐng)求,這個(gè)請(qǐng)求的意圖就是向服務(wù)器詢問“大哥,有新數(shù)據(jù)嗎?”。服務(wù)器在接收到請(qǐng)求后,根據(jù)實(shí)際情況(有數(shù)據(jù)或無數(shù)據(jù))做出響應(yīng):

      有數(shù)據(jù),我發(fā)給你;

      無數(shù)據(jù),你待會(huì)再問;

      這種一問一答的方式有著明顯的缺點(diǎn),即瀏覽器需要不斷的向服務(wù)器發(fā)出請(qǐng)求。由于 HTTP 請(qǐng)求包含較長的頭部信息(例如 User-Agent、Referer 和 Host 等),其中真正有效的數(shù)據(jù)可能只是很小的一部分,所以這樣會(huì)浪費(fèi)很多的帶寬資源。

      比輪詢更好的“數(shù)據(jù)實(shí)時(shí)更新”手段是 Comet。這種技術(shù)可以實(shí)現(xiàn)雙向通信,但依然需要反復(fù)發(fā)出請(qǐng)求。而且在 Comet 中,采用的是 HTTP 長連接,這同樣會(huì)消耗服務(wù)器資源。在這種情況下,HTML5 定義了更節(jié)省資源,且能夠讓雙端穩(wěn)定實(shí)時(shí)通信的 WebSocket 協(xié)議。在 WebSocket 協(xié)議下,客戶端和服務(wù)端只需要完成一次握手,就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。下圖描述了 WebSocket 協(xié)議中,雙端通信的過程:

      WebSocket 的優(yōu)點(diǎn)

      相對(duì)于 HTTP 協(xié)議來說,WebSocket 具有開銷少、實(shí)時(shí)性高、支持二進(jìn)制消息傳輸、支持?jǐn)U展和更好的壓縮等優(yōu)點(diǎn)。這些優(yōu)點(diǎn)如下所述:

      較少的開銷

      WebSocket 只需要一次握手,在每次傳輸數(shù)據(jù)時(shí)只傳輸數(shù)據(jù)幀即可。而 HTTP 協(xié)議下,每次請(qǐng)求都需要攜帶完整的請(qǐng)求頭信息,例如 User-Agent、Referer 和 Host 等。所以 WebSocket 的開銷相對(duì)于 HTTP 來說會(huì)少很多。

      更強(qiáng)的實(shí)時(shí)性

      由于協(xié)議是全雙工的,所以服務(wù)器可以隨時(shí)主動(dòng)給客戶端下發(fā)數(shù)據(jù)。相對(duì)于一問一答的 HTTP 來說,WebSocket 協(xié)議下的數(shù)據(jù)傳輸?shù)难舆t明顯更少。

      支持二進(jìn)制消息傳輸

      WebSocket 定義了二進(jìn)制幀,可以更輕松地處理二進(jìn)制內(nèi)容。

      支持?jǐn)U展

      開發(fā)者可以擴(kuò)展協(xié)議,或者實(shí)現(xiàn)部分自定義的子協(xié)議。

      更好的壓縮

      Websocket 在適當(dāng)?shù)臄U(kuò)展支持下,可以沿用之前內(nèi)容的上下文。這樣在傳遞類似結(jié)構(gòu)的數(shù)據(jù)時(shí),可以顯著地提高壓縮率。

      WebSocket 協(xié)議規(guī)范

      WebSocket 是一個(gè)通信協(xié)議,該協(xié)議的規(guī)范與標(biāo)準(zhǔn)均記錄在 RFC6455 中。協(xié)議共有 14 個(gè)部分,但與協(xié)議規(guī)范相關(guān)的只有 11 個(gè)部分:

      介紹

      術(shù)語和其他約定

      WebSocket URI

      握手規(guī)范

      數(shù)據(jù)幀

      發(fā)送和接收數(shù)據(jù)

      關(guān)閉連接

      錯(cuò)誤處理

      擴(kuò)展

      通信安全

      注意事項(xiàng)

      而與本篇 Chat 相關(guān)的為 4、5、6、7 部分的內(nèi)容,這些也是 WebSocket 中較為重要的內(nèi)容。接下來,我們就來學(xué)習(xí)這些知識(shí)。

      雙端交互流程

      客戶端與服務(wù)端連接成功之前,使用的通信協(xié)議是 HTTP。連接成功后,使用的才是 WebSocket 協(xié)議。下圖描述了雙端交互的流程:

      首先,客戶端向服務(wù)端發(fā)出一個(gè) HTTP 請(qǐng)求,請(qǐng)求中攜帶了服務(wù)端規(guī)定的信息,并在信息中表明希望將協(xié)議升級(jí)為 WebSocket。這個(gè)請(qǐng)求被稱為升級(jí)請(qǐng)求,雙端升級(jí)協(xié)議的整個(gè)過程叫做握手。然后服務(wù)端驗(yàn)證客戶端發(fā)送的信息,如果符合規(guī)范則將協(xié)議替換成 WebSocket,并將升級(jí)成功的信息響應(yīng)給客戶端。最后,雙方就可以基于 WebSocket 協(xié)議互相推送信息了。現(xiàn)在,我們需要學(xué)習(xí)的第一個(gè)知識(shí)點(diǎn)就是握手。

      我們先來看看 RFC6455 對(duì)客戶端握手的規(guī)定,原文錨點(diǎn)鏈接為 Opening Handshak。此段原文如下:

      The?opening?handshake?is?intended?to?be?compatible?with?HTTP-based?server-side?software?and?intermediaries,?so?that?a?single?port?can?be?used?by?both?HTTP?clients?talking?to?that?server?and?WebSocket?clients?talking?to?that?server.??To?this?end,?the?WebSocket?client's?handshake?is?an?HTTP?Upgrade?request:?GET?/chat?HTTP/1.1?Host:?server.example.com?Upgrade:?websocket?Connection:?Upgrade?Sec-WebSocket-Key:?dGhlIHNhbXBsZSBub25jZQ==?Origin:?http://example.com?Sec-WebSocket-Protocol:?chat,?superchat?Sec-WebSocket-Version:?13?In?compliance?with?[RFC2616],?header?fields?in?the?handshake?may?be?sent?by?the?client?in?any?order,?so?the?order?in?which?different?header?fields?are?received?is?not?significant.?復(fù)制代碼

      原文表明,握手時(shí)使用的并不是 WebSocekt 協(xié)議,而是 HTTP 協(xié)議,握手時(shí)發(fā)出的請(qǐng)求叫做升級(jí)請(qǐng)求。客戶端在握手階段通過 Connection 和 Upgrade 頭域及對(duì)應(yīng)的值告知服務(wù)端,要求將當(dāng)前通信協(xié)議升級(jí)為指定協(xié)議,此處指定的是 WebSocket 協(xié)議。其他頭域名及值的作用如下:

      GET /chat HTTP/1.1 表明本次請(qǐng)求基于 HTTP/1.1,請(qǐng)求方式為 GET;

      Sec-WebSocket-Protocol 用于指定子協(xié)議;

      Sec-WebSocket-Version 表明協(xié)議版本,要求雙端版本一致。當(dāng)前 WebSocekt 協(xié)議版本默認(rèn)為 13;

      Origin 表明請(qǐng)求來自于哪個(gè)站點(diǎn);

      Host 表明目標(biāo)主機(jī);

      Sec-WebSocket-Key 用于防止攻擊者惡意欺騙服務(wù)端;

      也就是說,握手時(shí)客戶端只需要按照上述規(guī)定向服務(wù)端發(fā)出一個(gè) HTTP 請(qǐng)求即可。

      服務(wù)端收到客戶端發(fā)起的請(qǐng)求后,按照 RFC6455 的約定驗(yàn)證請(qǐng)求信息。驗(yàn)證通過就代表握手成功,此時(shí)服務(wù)端應(yīng)當(dāng)按照約定將以下內(nèi)容響應(yīng)給客戶端:

      HTTP/1.1?101?Switching?Protocols??Upgrade:?websocket??Connection:?Upgrade??Sec-WebSocket-Accept:?s3pPLMBiTxaQ9kYGzzhZRbK+xOo=??Sec-WebSocket-Protocol:?chat?復(fù)制代碼

      服務(wù)端會(huì)給出代表連接結(jié)果的響應(yīng)狀態(tài)碼,101 狀態(tài)碼表示表示本次請(qǐng)求成功且得到服務(wù)端的正確處理。Connection 和 Upgrade 表示已經(jīng)切換成 websocket 協(xié)議。Sec-WebSocket-Accept 則是經(jīng)過服務(wù)器確認(rèn),并且加密過后的 Sec-WebSocket-Key,這個(gè)值根據(jù)客戶端發(fā)送的 Sec-WebSocket-Key 生成。Sec-WebSocket-Protocol 表明雙端約定的子協(xié)議。

      這樣,客戶端與服務(wù)端就完成了握手操作。雙端達(dá)成一致,通信協(xié)議將由 HTTP 協(xié)議切換成 WebSocket 協(xié)議。

      雙方握手成功,并確定協(xié)議后,就可以互相發(fā)送信息了。客戶端和服務(wù)端互發(fā)消息與我們平時(shí)在社交應(yīng)用中互發(fā)消息類似,例如:

      client:?Hello,?Server?boy.?server:?Hello,?Client?Man.?復(fù)制代碼

      當(dāng)然,這里的 Hello, Server boy 和 Hello, Client Man 是有助于我們理解的比喻。實(shí)際上,WebSocket 協(xié)議的中的數(shù)據(jù)傳輸格式并不是這樣直接呈現(xiàn)的。

      WebSocket 雙端傳輸?shù)氖且粋€(gè)個(gè)數(shù)據(jù)幀,數(shù)據(jù)幀的約定原文如下:

      In?the?WebSocket?Protocol,?data?is?transmitted?using?a?sequence?of?frames.?To?avoid?confusing?network?intermediaries?(such?as?intercepting?proxies)?and?for?security?reasons?that?are?further?discussed?in?Section?10.3,?a?client?MUST?mask?all?frames?that?it?sends?to?the?server?(see?Section?5.3?for?further?details).?(Note?that?masking?is?done?whether?or?not?the?WebSocket?Protocol?is?running?over?TLS.)??The?server?MUST?close?the?connection?upon?receiving?a?frame?that?is?not?masked.??In?this?case,?a?server?MAY?send?a?Close?frame?with?a?status?code?of?1002?(protocol?error)?as?defined?in?Section?7.4.1.??A?server?MUST?NOT?mask?any?frames?that?it?sends?to?the?client.??A?client?MUST?close?a?connection?if?it?detects?a?masked?frame.??In?this?case,?it?MAY?use?the?status?code?1002?(protocol?error)?as?defined?in?Section?7.4.1.??(These?rules?might?be?relaxed?in?a?future?specification.)?The?base?framing?protocol?defines?a?frame?type?with?an?opcode,?a?payload?length,?and?designated?locations?for?"Extension?data"?and?"Application?data",?which?together?define?the?"Payload?data".?Certain?bits?and?opcodes?are?reserved?for?future?expansion?of?the?protocol.?A?data?frame?MAY?be?transmitted?by?either?the?client?or?the?server?at?any?time?after?opening?handshake?completion?and?before?that?endpoint?has?sent?a?Close?frame?(Section?5.5.1).?復(fù)制代碼

      原文表明,協(xié)議中約定數(shù)據(jù)傳輸時(shí)并不是使用 Unicode 編碼,而是使用數(shù)據(jù)幀(Frame)。下圖描述了數(shù)據(jù)幀的組成:

      數(shù)據(jù)幀由幾個(gè)部分組成:FIN、RSV1、RSV2、RSV3、opcode、MASK、Payload length、Payload Data、和 Masking-key。下面,我們來了解一下數(shù)據(jù)幀組件的大體含義或作用。

      FIN

      占 1 bit,其值為 0 或 1,值對(duì)應(yīng)的含義如下:

      0:不是消息的最后一個(gè)分片;?1:是消息的最后一個(gè)分片;?復(fù)制代碼

      RSV1 RSV2 RSV3

      均占 1 bit,一般情況下值為 0。當(dāng)客戶端、服務(wù)端協(xié)商采用 WebSocket 擴(kuò)展時(shí),這三個(gè)標(biāo)志位可以非 0,且值的含義由擴(kuò)展進(jìn)行定義。如果出現(xiàn)非零的值,但并沒有采用 WebSocket 擴(kuò)展,則連接出錯(cuò)。

      Opcode

      占 4 bit,其值可以是 %x0、%x1、%x2、%x3~7、%x8、%x9、%xA 和 %xB~F 中的任何一個(gè)。值對(duì)應(yīng)的含義如下:

      %x0:表示一個(gè)延續(xù)幀。當(dāng)?Opcode?為?0?時(shí),表示本次數(shù)據(jù)傳輸采用了數(shù)據(jù)分片,當(dāng)前收到的數(shù)據(jù)幀為其中一個(gè)數(shù)據(jù)分片;?%x1:表示這是一個(gè)文本幀(text?frame);?%x2:表示這是一個(gè)二進(jìn)制幀(binary?frame);?%x3-7:保留的操作代碼,用于后續(xù)定義的非控制幀;?%x8:表示連接斷開,是一個(gè)控制幀;?%x9:表示這是一個(gè)心跳請(qǐng)求(ping);?%xA:表示這是一個(gè)心跳響應(yīng)(pong);?%xB-F:保留的操作代碼,用于后續(xù)定義的控制幀;?復(fù)制代碼

      Mask

      占 1 bit,其值為 0 或 1。值 0 表示要對(duì)數(shù)據(jù)進(jìn)行掩碼異或操作,反之亦然。

      Payload length

      占 7 bit 或 7+16 bit 或 7+64 bit,表示數(shù)據(jù)的長度,其值可以是0~127 中的任何一個(gè)數(shù)。值對(duì)應(yīng)的含義如下:

      0~126:數(shù)據(jù)的長度等于該值;?126:后續(xù)?2?個(gè)字節(jié)代表一個(gè)?16?位的無符號(hào)整數(shù),該無符號(hào)整數(shù)的值為數(shù)據(jù)的長度;?127:后續(xù)?8?個(gè)字節(jié)代表一個(gè)?64?位的無符號(hào)整數(shù)(最高位為?0),該無符號(hào)整數(shù)的值為數(shù)據(jù)的長度。?復(fù)制代碼

      掩碼

      掩碼的作用并不是為了防止數(shù)據(jù)泄密,而是為了防止早期版本的協(xié)議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)問題。這里要注意的是從客戶端向服務(wù)端發(fā)送數(shù)據(jù)時(shí),需要對(duì)數(shù)據(jù)進(jìn)行掩碼操作;從服務(wù)端向客戶端發(fā)送數(shù)據(jù)時(shí),不需要對(duì)數(shù)據(jù)進(jìn)行掩碼操作。

      如果服務(wù)端接收到的數(shù)據(jù)沒有進(jìn)行過掩碼操作,服務(wù)端需要斷開連接。如果Mask是1,那么在Masking-key中會(huì)定義一個(gè)掩碼鍵(masking key),并用這個(gè)掩碼鍵來對(duì)數(shù)據(jù)載荷進(jìn)行反掩碼。

      所有客戶端發(fā)送到服務(wù)端的數(shù)據(jù)幀,Mask都是1。

      掩碼算法:按位做循環(huán)異或運(yùn)算,先對(duì)該位的索引取模來獲得 Masking-key 中對(duì)應(yīng)的值 x,然后對(duì)該位與 x 做異或,從而得到真實(shí)的 byte 數(shù)據(jù)。

      Making-key

      占 0 或 4 bytes,其值為 0 或 1。值對(duì)應(yīng)的含義如下:

      0:沒有?Masking-key;?1:有?Masking-key;?復(fù)制代碼

      Payload Data

      雙端接收到數(shù)據(jù)幀之后,可以根據(jù)上述幾個(gè)數(shù)據(jù)幀組件的值對(duì) Payload Data 進(jìn)行處理或直接提取數(shù)據(jù)。

      在了解到 WebSocket 傳輸?shù)臄?shù)據(jù)幀格式后,我們?cè)賮韺W(xué)習(xí)數(shù)據(jù)收發(fā)的流程。在雙端建立 WebSocket 連接后,任何一端都可以給另一端發(fā)送消息,這里的消息指的就是數(shù)據(jù)幀。但平時(shí)我們輸入或輸出的信息都是“明文”,所以在消息發(fā)送前需要將“明文”通過一定的方法轉(zhuǎn)換成數(shù)據(jù)幀。而在接收端,拿到數(shù)據(jù)幀后需要按照一定的規(guī)則將數(shù)據(jù)幀轉(zhuǎn)換為”明文“。下圖描述了雙端收發(fā) Hello, world 的主要流程:

      保持連接和關(guān)閉連接

      WebSocket 雙端的連接可以保持長期不斷開,但實(shí)際應(yīng)用中卻不會(huì)這么做。如果保持所有連接不斷開,但連接中有很多不活躍的成員,那么就會(huì)造成嚴(yán)重的資源浪費(fèi)。

      服務(wù)端如何判斷客戶端是否活躍呢?

      服務(wù)端會(huì)定期給所有的客戶端發(fā)送一個(gè) opcode 為 %x9 的數(shù)據(jù)幀,這個(gè)數(shù)據(jù)幀被稱為 Ping 幀。客戶端在收到 Ping 幀時(shí),必須回復(fù)一個(gè) opcode 為 %xA 的數(shù)據(jù)幀(又稱為 Pong 幀),否則服務(wù)端就可以主動(dòng)斷開連接。反之,如果服務(wù)端在發(fā)送 Ping 幀后能夠得到客戶端 Pong 幀的回應(yīng),就代表這個(gè)客戶端是活躍的,不要斷開連接。

      如果需要關(guān)閉連接,那么一端向另一端發(fā)送 opcode 為 %x8 的數(shù)據(jù)幀即可,這個(gè)數(shù)據(jù)幀被稱為關(guān)閉幀。

      插個(gè)廣告

      如果覺得本篇文章對(duì)你有幫助,希望你能到 GitChat 上訂閱我發(fā)表的 Chat,支持我繼續(xù)分享高質(zhì)量文章。

      GitChat 《開發(fā)者必知必會(huì)的 WebSocket 協(xié)議》

      GitChat《MongoDB 實(shí)戰(zhàn)教程:數(shù)據(jù)庫與集合的 CRUD 操作篇》

      實(shí)際代碼解讀-Python

      上面所述均為 RFC6455 中約定的 WebSocket 協(xié)議規(guī)范。在學(xué)習(xí)完理論知識(shí)后,我們可以通過一些示例(代碼偽代碼)來加深對(duì)上述知識(shí)的理解。

      Echo Test 是 websocket.org 提供的一個(gè)測(cè)試平臺(tái),開發(fā)者可以用它測(cè)試與 WebSocket 相關(guān)的連接、消息發(fā)送和消息接收等功能。下面的代碼演示也將基于 Echo Test。

      客戶端握手

      上面提到過,客戶端向服務(wù)端發(fā)出升級(jí)請(qǐng)求時(shí),請(qǐng)求頭如下:

      GET?/chat?HTTP/1.1?Host:?server.example.com?Upgrade:?websocket?Connection:?Upgrade?Sec-WebSocket-Key:?dGhlIHNhbXBsZSBub25jZQ==?Origin:?http://example.com?Sec-WebSocket-Protocol:?chat,?superchat?Sec-WebSocket-Version:?13?復(fù)制代碼

      對(duì)應(yīng)的 Python 代碼如下:

      云享專家韋世東:開發(fā)者必知必會(huì)的 WebSocket 協(xié)議

      import?requests?url?=?'http://echo.websocket.org/?encoding=text'?header?=?{?????"Host":?"echo.websocket.org",?????"Upgrade":?"websocket",?????"Connection":?"Upgrade",?????"Sec-WebSocket-Key":?"9GxOnSwEuBNbLeBwiltymg==",?????"Origin":?"http://www.websocket.or",?????"Sec-WebSocket-Protocol":?"chat,?superchat",?????"Sec-WebSocket-Version":?"13"?}?resp?=?requests.get(url,?headers=header)?print(resp.status_code)?復(fù)制代碼

      代碼運(yùn)行后返回的結(jié)果為 101,這說明上方代碼完成了升級(jí)請(qǐng)求的工作。

      數(shù)據(jù)轉(zhuǎn)換為數(shù)據(jù)幀

      數(shù)據(jù)轉(zhuǎn)換為數(shù)據(jù)幀涉及到很多知識(shí),同時(shí)需要運(yùn)行完整的 WebSocket 客戶端。本篇 Chat 不演示完整的代碼結(jié)構(gòu),僅講解對(duì)應(yīng)的代碼邏輯。完整的 WebSocket 客戶端可在 Github 上克隆我編寫的開源的庫:aiowebsocekt。

      克隆到本地后打開 freams.py ,這就是負(fù)責(zé)數(shù)據(jù)幀的轉(zhuǎn)換處理的主要文件。

      首先看 write() 方法,發(fā)送端發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)會(huì)經(jīng)過該方法。write() 方法的完整代碼如下:

      async?def?write(self,?fin,?code,?message,?mask=True,?rsv1=0,?rsv2=0,?rsv3=0):?????????"""Converting?messages?to?data?frames?and?sending?them.?????????Client?data?frames?must?be?masked,so?mask?is?True.?????????"""?????????head1,?head2?=?self.pack_message(fin,?code,?mask,?rsv1,?rsv2,?rsv3)?????????output?=?io.BytesIO()?????????length?=?len(message)?????????if?length?

      首先,調(diào)用 pack_message() 方法構(gòu)造數(shù)據(jù)幀中的 FIN、Opcode、RSV1、RSV2、RSV3。然后根據(jù)消息的長度構(gòu)造數(shù)據(jù)幀中的 Payload length。接著根據(jù)發(fā)送端是客戶端或服務(wù)端對(duì)數(shù)據(jù)進(jìn)行掩碼。最后將數(shù)據(jù)放到數(shù)據(jù)幀中,并將數(shù)據(jù)幀發(fā)送給接收端。這里用到的 pack_message() 方法代碼如下:

      @staticmethod?????def?pack_message(fin,?code,?mask,?rsv1=0,?rsv2=0,?rsv3=0):?????????"""Converting?message?into?data?frames?????????conversion?rule?reference?document:?????????https://tools.ietf.org/html/rfc6455#section-5.2?????????"""?????????head1?=?(?????????????????(0b10000000?if?fin?else?0)?????????????????|?(0b01000000?if?rsv1?else?0)?????????????????|?(0b00100000?if?rsv2?else?0)?????????????????|?(0b00010000?if?rsv3?else?0)?????????????????|?code?????????)?????????head2?=?0b10000000?if?mask?else?0??#?Whether?to?mask?or?not?????????return?head1,?head2?復(fù)制代碼

      用于執(zhí)行掩碼操作的 message_mask() 方法代碼如下:

      @staticmethod?????def?message_mask(message:?bytes,?mask):?????????if?len(mask)?!=?4:?????????????raise?FrameError("The?'mask'?must?contain?4?bytes")?????????return?bytes(b?^?m?for?b,?m?in?zip(message,?cycle(mask)))?復(fù)制代碼

      以上就是數(shù)據(jù)轉(zhuǎn)換為數(shù)據(jù)幀并發(fā)送給接收端的主要代碼。

      數(shù)據(jù)幀轉(zhuǎn)換為數(shù)據(jù)

      同樣是 freams.py 文件,這次我們來看 read() 方法。接收端接收數(shù)據(jù)后,數(shù)據(jù)會(huì)經(jīng)過該方法。read() 方法的完整代碼如下:

      async?def?read(self,?text=False,?mask=False,?maxsize=None):?????????"""return?information?about?message?????????"""?????????fin,?code,?rsv1,?rsv2,?rsv3,?message?=?await?self.unpack_frame(mask,?maxsize)?????????await?self.extra_operation(code,?message)??#?根據(jù)操作碼決定后續(xù)操作?????????if?any([rsv1,?rsv2,?rsv3]):?????????????logging.warning('RSV?not?0')?????????if?not?fin:?????????????logging.warning('Fragmented?control?frame:Not?FIN')?????????if?code?is?DataFrames.binary.value?and?text:?????????????if?isinstance(message,?bytes):?????????????????message?=?message.decode()?????????if?code?is?DataFrames.text.value?and?not?text:?????????????if?isinstance(message,?str):?????????????????message?=?message.encode()?????????return?message?復(fù)制代碼

      首先,調(diào)用 unpack_frame() 方法從數(shù)據(jù)幀中提取出 FIN、Opcode、RSV1、RSV2、RSV3 和 Payload Data(代碼中是 message)。然后根據(jù) Opcode 決定后續(xù)的操作,例如提取數(shù)據(jù)、關(guān)閉連接、發(fā)送 Ping 幀或 Pong 幀等。

      unpack_frame() 方法的完整代碼如下:

      async?def?unpack_frame(self,?mask=False,?maxsize=None):?????????reader?=?self.reader.readexactly?????????frame_header?=?await?reader(2)?????????head1,?head2?=?unpack('!BB',?frame_header)?????????fin?=?True?if?head1?&?0b10000000?else?False?????????rsv1?=?True?if?head1?&?0b01000000?else?False?????????rsv2?=?True?if?head1?&?0b00100000?else?False?????????rsv3?=?True?if?head1?&?0b00010000?else?False?????????code?=?head1?&?0b00001111?????????if?(True?if?head2?&?0b10000000?else?False)?!=?mask:?????????????raise?FrameError("Incorrect?masking")?????????length?=?head2?&?0b01111111?????????if?length?==?126:?????????????message?=?await?reader(2)?????????????length,?=?unpack('!H',?message)?????????elif?length?==?127:?????????????message?=?await?reader(8)?????????????length,?=?unpack('!Q',?message)?????????if?maxsize?and?length?>?maxsize:?????????????raise?FrameError("Message?length?is?too?long)".format(length,?maxsize))?????????if?mask:?????????????mask_bits?=?await?reader(4)?????????message?=?self.message_mask(message,?mask_bits)?if?mask?else?await?reader(length)?????????return?fin,?code,?rsv1,?rsv2,?rsv3,?message?復(fù)制代碼

      從數(shù)據(jù)幀中提取 FIN、RSV1、Opcode 和 Payload Data(代碼中是 message) 等組件時(shí),使用的是按位與運(yùn)算。對(duì)位運(yùn)算不太了解的朋友可以查閱我之前在微信公眾號(hào)發(fā)表的《七分鐘全面了解位運(yùn)算》文章。接著根據(jù)是否掩碼調(diào)用 message_mask() 方法,最后將得到的組件返回給調(diào)用方。

      總結(jié)

      本篇 Chat 我們了解了 WebSocekt 協(xié)議的來源,并討論了它的優(yōu)點(diǎn)。然后解讀 RFC6455 中對(duì) WebSocket 的約定,了解到雙端交互流程、保持連接和關(guān)閉連接方面的知識(shí)。最后學(xué)習(xí)到如何將 WebSocket 協(xié)議轉(zhuǎn)換為具體的代碼。

      WebSocket 有幾個(gè)關(guān)鍵點(diǎn):握手、數(shù)據(jù)與數(shù)據(jù)幀的轉(zhuǎn)換、保持連接的 Ping 幀和 Pong 幀、主動(dòng)關(guān)閉連接的關(guān)閉幀。希望大家在看過本篇 Chat 后,能夠?qū)?WebSocket 協(xié)議有一個(gè)全新的認(rèn)識(shí)。

      websocket 開發(fā)者 TCP/IP

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:訂單生產(chǎn)管理系統(tǒng)(查訂單系統(tǒng))
      下一篇:微服務(wù)架構(gòu) — 服務(wù)治理 — 服務(wù)監(jiān)控與告警、服務(wù)日志與審計(jì)
      相關(guān)文章
      亚洲人成人网站在线观看| 中文字幕亚洲精品| 午夜亚洲www湿好大| 亚洲一级特黄大片无码毛片 | 亚洲AV成人一区二区三区观看 | 亚洲国产精品一区二区久久| 亚洲av永久无码精品漫画| 亚洲色婷婷综合久久| 国产精品亚洲二区在线| 亚洲日韩精品国产一区二区三区| 亚洲永久在线观看| 亚洲熟女精品中文字幕| 亚洲精品国产国语| 亚洲日日做天天做日日谢| 亚洲黄色激情视频| 亚洲欧洲另类春色校园网站| 国产精品亚洲午夜一区二区三区| 国产精品高清视亚洲一区二区| 久久亚洲国产成人影院| 亚洲精品乱码久久久久蜜桃| 国产午夜亚洲精品| 麻豆亚洲AV成人无码久久精品 | 亚洲成人免费网址| 亚洲av无码一区二区三区观看| 2020亚洲男人天堂精品| 亚洲色成人WWW永久在线观看| 亚洲精品V天堂中文字幕| 亚洲国产精品久久久久秋霞小| 国产亚洲综合精品一区二区三区| 亚洲精品国产日韩无码AV永久免费网 | 亚洲国产一区二区三区青草影视| 中文字幕亚洲综合久久| 亚洲一级片在线播放| 亚洲码和欧洲码一码二码三码| 香蕉视频亚洲一级| 最新精品亚洲成a人在线观看| 亚洲AV无码久久精品成人 | www.亚洲精品.com| 国产亚洲AV夜间福利香蕉149 | 激情无码亚洲一区二区三区| 亚洲成a人无码av波多野按摩|