關(guān)于我們

質量為(wèi)本、客戶為(wèi)根、勇于拼搏、務(wù)實創新(xīn)

< 返回新(xīn)聞公共列表

一(yī)個(gè) WebSocket 服務(wù)器(qì)是如(rú)何開發出來(lái)的? 原創:

發布時(shí)間(jiān):2019-09-02 13:54:50

WebSocket 協議(yì)是為(wèi)了(le)解決 http 協議(yì)的無狀态、短連接(通常是)和服務(wù)端無法主動給客戶端推送數據等問題而開發的新(xīn)型協議(yì),其通信基礎也(yě)是基于 TCP。由于較舊的浏覽器(qì)可能(néng)不支持 WebSocket 協議(yì),所以使用 WebSocket 協議(yì)的通信雙方在進行 TCP 三次握手之後,還要再額外地進行一(yī)次握手,這(zhè)一(yī)次的握手通信雙方的報(bào)文格式是基于 HTTP 協議(yì)改造的。

WebSocket 握手過程

TCP 三次握手的過程我們就(jiù)不在這(zhè)裏贅述了(le),任何一(yī)本網絡通信書籍上(shàng)都有詳細的介紹。我們這(zhè)裏來(lái)介紹一(yī)下(xià) WebSocket 通信最後一(yī)次的握手過程。

握手開始後,一(yī)方給另外一(yī)方發送一(yī)個(gè) http 協議(yì)格式的報(bào)文,這(zhè)個(gè)報(bào)文格式大緻如(rú)下(xià):

 1GET /realtime HTTP/1.1\r\n
2Host: 127.0.0.1:9989\r\n
3Connection: Upgrade\r\n
4Pragma: no-cache\r\n
5Cache-Control: no-cache\r\n
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n
7Upgrade: websocket\r\n
8Origin: http://xyz.com\r\n
9Sec-WebSocket-Version: 13\r\n
10Accept-Encoding: gzip, deflate, br\r\n
11Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
12Sec-WebSocket-Key: IqcAWodjyPDJuhGgZwkpKg==\r\n
13Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n
14\r\n

對這(zhè)個(gè)格式有如(rú)下(xià)要求:

  • 握手必須是一(yī)個(gè)有效的 HTTP 請求;

  • 請求的方法必須為(wèi) GET,且 HTTP 版本必須是 1.1;

  • 請求必須包含 Host 字段信息;

  • 請求必須包含 Upgrade字段信息,值必須為(wèi) websocket;

  • 請求必須包含 Connection 字段信息,值必須為(wèi) Upgrade;

  • 請求必須包含 Sec-WebSocket-Key 字段,該字段值是客戶端的标識編碼成 base64 格式

  • 請求必須包含 Sec-WebSocket-Version 字段信息,值必須為(wèi) 13;

  • 請求必須包含 Origin 字段;

  • 請求可能(néng)包含 Sec-WebSocket-Protocol 字段,規定子(zǐ)協議(yì);

  • 請求可能(néng)包含 Sec-WebSocket-Extensions字段規定協議(yì)擴展;

  • 請求可能(néng)包含其他字段,如(rú) cookie 等。

對端收到該數據包後如(rú)果支持 WebSocket 協議(yì),會回複一(yī)個(gè) http 格式的應答(dá),這(zhè)個(gè)應答(dá)報(bào)文的格式大緻如(rú)下(xià):

1HTTP/1.1 101 Switching Protocols\r\n
2Upgrade: websocket\r\n
3Connection: Upgrade\r\n
4Sec-WebSocket-Accept: 5wC5L6joP6tl31zpj9OlCNv9Jy4=\r\n
5\r\n

上(shàng)面列出了(le)應答(dá)報(bào)文中必須包含的幾個(gè)字段和對應的值,即 UpgradeConnectionSec-WebSocket-Accept,注意:第一(yī)行必須是 HTTP/1.1 101 Switching Protocols\r\n

對于字段 Sec-WebSocket-Accept 字段,其值是根據對端傳過來(lái)的 Sec-WebSocket-Key 的值經過一(yī)定的算(suàn)法計算(suàn)出來(lái)的,這(zhè)樣應答(dá)的雙方才能(néng)匹配。算(suàn)法如(rú)下(xià):

  1. 将 Sec-WebSocket-Key 值與固定字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 進行拼接;

  2. 将拼接後的字符串進行 SHA-1 處理(lǐ),然後将結果再進行 base64 編碼。

算(suàn)法公式:

1mask  = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";  // 這(zhè)是算(suàn)法中要用到的固定字符串
2accept = base64( sha1( Sec-WebSocket-Key + mask ) );

我用 C++ 實現(xiàn)了(le)該算(suàn)法:

  1namespace uWS {
 2
 3struct WebSocketHandshake {
 4    template <int N, typename T>
 5    struct static_for {
 6        void operator()(uint32_t *a, uint32_t *b) {
 7            static_for<N - 1, T>()(a, b);
 8            T::template f<N - 1>(a, b);
 9        }
10    };
11
12    template <typename T>
13    struct static_for<0, T> {
14        void operator()(uint32_t *a, uint32_t *hash) {}
15    };
16
17    template <int state>
18    struct Sha1Loop {
19        static inline uint32_t rol(uint32_t value, size_t bits) {return (value << bits) | (value >> (32 - bits));}
20        static inline uint32_t blk(uint32_t b[16], size_t i) {
21            return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
22        }
23
24        template <int i>
25        static inline void f(uint32_t *a, uint32_t *b) {
26            switch (state) {
27            case 1:
28                a[i % 5] += ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
29                a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
30                break;
31            case 2:
32                b[i] = blk(b, i);
33                a[(1 + i) % 5] += ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
34                a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
35                break;
36            case 3:
37                b[(i + 4) % 16] = blk(b, (i + 4) % 16);
38                a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
39                a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
40                break;
41            case 4:
42                b[(i + 8) % 16] = blk(b, (i + 8) % 16);
43                a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | (a[(3 + i) % 5] & a[(2 + i) % 5])) + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
44                a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
45                break;
46            case 5:
47                b[(i + 12) % 16] = blk(b, (i + 12) % 16);
48                a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
49                a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
50                break;
51            case 6:
52                b[i] += a[4 - i];
53            }
54        }
55    };
56
57    /**
58     * sha1 函數的實現(xiàn)
59     */

60    static inline void sha1(uint32_t hash[5], uint32_t b[16]) {
61        uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
62        static_for<16, Sha1Loop<1>>()(a, b);
63        static_for<4, Sha1Loop<2>>()(a, b);
64        static_for<20, Sha1Loop<3>>()(a, b);
65        static_for<20, Sha1Loop<4>>()(a, b);
66        static_for<20, Sha1Loop<5>>()(a, b);
67        static_for<5, Sha1Loop<6>>()(a, hash);
68    }
69
70    /**
71     * base64 編碼函數
72     */

73    static inline void base64(unsigned char *src, char *dst) {
74        const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
75        for (int i = 0; i < 18; i += 3) {
76            *dst++ = b64[(src[i] >> 2) & 63];
77            *dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
78            *dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
79            *dst++ = b64[src[i + 2] & 63];
80        }
81        *dst++ = b64[(src[18] >> 2) & 63];
82        *dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
83        *dst++ = b64[((src[19] & 15) << 2)];
84        *dst++ = '=';
85    }
86
87public:
88    /** 
89     * 生(shēng)成 Sec-WebSocket-Accept 算(suàn)法
90     * @param input 對端傳過來(lái)的Sec-WebSocket-Key值
91     * @param output 存放(fàng)生(shēng)成的 Sec-WebSocket-Accept 值
92     */

93    static inline void generate(const char input[24], char output[28]) {
94        uint32_t b_output[5] = {
95            0x674523010xefcdab890x98badcfe0x103254760xc3d2e1f0
96        };
97        uint32_t b_input[16] = {
98            0000000x323538450x414641350x2d4539310x342d34370x44412d39,
99            0x3543412d0x433541420x304443380x354231310x80000000
100        };
101
102        for (int i = 0; i < 6; i++) {
103            b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
104        }
105        sha1(b_output, b_input);
106        uint32_t last_b[16] = {000000000000000480};
107        sha1(b_output, last_b);
108        for (int i = 0; i < 5; i++) {
109            uint32_t tmp = b_output[i];
110            char *bytes = (char *) &b_output[i];
111            bytes[3] = tmp & 0xff;
112            bytes[2] = (tmp >> 8) & 0xff;
113            bytes[1] = (tmp >> 16) & 0xff;
114            bytes[0] = (tmp >> 24) & 0xff;
115        }
116        base64((unsigned char *) b_output, output);
117    }
118};

握手完成之後,通信雙方就(jiù)可以保持連接并相互發送數據了(le)。

WebSocket 協議(yì)格式

WebSocket 協議(yì)格式的 RFC 文檔可以參見:[]https://tools.ietf.org/html/rfc6455。

常聽(tīng)人(rén)說(shuō) WebSocket 協議(yì)是基于 http 協議(yì)的,因此我在剛接觸 WebSocket 協議(yì)時(shí)總以為(wèi)每個(gè) WebSocket 數據包都是 http 格式,其實不然,WebSocket 協議(yì)除了(le)上(shàng)文中提到的這(zhè)次握手過程中使用的數據格式是 http 協議(yì)格式,之後的通信雙方使用的是另外一(yī)種自定義格式。每一(yī)個(gè) WebSocket 數據包我們稱之為(wèi)一(yī)個(gè) Frame(幀),其格式圖如(rú)下(xià):

640.webp (1).jpg

我們來(lái)逐一(yī)介紹一(yī)下(xià)上(shàng)文中各字段的含義:

第一(yī)個(gè)字節内容:

  • FIN 标志,占第一(yī)個(gè)字節中的第一(yī)位(bit),即一(yī)字節中的最高位(一(yī)字節等于 8 位),該标志置 0 時(shí)表示當前包未結束後續有該包的分(fēn)片,置 1 時(shí)表示當前包已結束後續無該包的分(fēn)片。我們在解包時(shí),如(rú)果發現(xiàn)該标志為(wèi) 1,則需要将當前包的“包體(tǐ)”數據(即圖中 Payload Data)緩存起來(lái),與後續包分(fēn)片組裝在一(yī)起,才是一(yī)個(gè)完整的包體(tǐ)數據。

  • RSV1RSV2RSV3 每個(gè)占一(yī)位,一(yī)共三位,這(zhè)三個(gè)位是保留字段(默認都是 0),你可以用它們作(zuò)為(wèi)通信的雙方協商(shāng)好(hǎo)(hǎo)的一(yī)些(xiē)特殊标志;

  • opCode 操作(zuò)類型,占四位,目前操作(zuò)類型及其取值如(rú)下(xià):

     1// 4 bits
    2enum OpCode
    3{
    4  //表示後續還有新(xīn)的 Frame
    5  CONTINUATION_FRAME  = 0x0,
    6  //包體(tǐ)是文本類型的Frame
    7  TEXT_FRAME          = 0x1,
    8  //包體(tǐ)是二進制類型的 Frame
    9  BINARY_FRAME        = 0x2,
    10  //保留值
    11  RESERVED1           = 0x3,
    12  RESERVED2           = 0x4,
    13  RESERVED3           = 0x5,
    14  RESERVED4           = 0x6,
    15  RESERVED5           = 0x7,
    16  //建議(yì)對端關(guān)閉的 Frame
    17  CLOSE               = 0x8,
    18  //心跳(tiào)包中的 ping Frame
    19  PING                = 0x9,
    20  //心跳(tiào)包中的 pong Frame
    21  PONG                = 0xA,
    22  //保留值
    23  RESERVED6           = 0xB,
    24  RESERVED7           = 0xC,
    25  RESERVED8           = 0xD,
    26  RESERVED9           = 0xE,
    27  RESERVED10          = 0xF
    28};

第二個(gè)字節内容:

  • mask 标志,占一(yī)位,該标志為(wèi) 1 時(shí),表明該 Frame 在包體(tǐ)長度字段後面攜帶 4 個(gè)字節的 masking-key 信息,為(wèi) 0 時(shí)則沒有 masking-key 信息。masking-key 信息下(xià)文會介紹。

  • Payload len,占七位,該字段表示包體(tǐ)的長度信息。由于 Payload length 值使用了(le)一(yī)個(gè)字節的低(dī)七位(7 bit),因此其能(néng)表示的長度範圍是 0 ~ 127,其中 126 和 127 被當做特殊标志使用。

    當該字段值是 0~125 時(shí),表示跟在 masking-key 字段後面的就(jiù)是包體(tǐ)内容長度;當該值是 126 時(shí),接下(xià)來(lái)的 2 個(gè)字節内容表示跟在 masking-key 字段後面的包體(tǐ)内容的長度(即圖中的 Extended Payload Length)。由于 2 個(gè)字節最大表示的無符号整數是 0xFFFF(十進制是 65535, 編譯器(qì)提供了(le)一(yī)個(gè)宏 UINT16_MAX 來(lái)表示這(zhè)個(gè)值)。如(rú)果包體(tǐ)長度超過 65535,包長度就(jiù)記錄不下(xià)了(le),此時(shí)應該将 Payload length 設置為(wèi) 127,以使用更多的字節數來(lái)表示包體(tǐ)長度。

    當 Payload length 是 127 時(shí),接下(xià)來(lái)則用 8 個(gè)字節内容表示跟在 masking-key 字段後面的包體(tǐ)内容的長度(Extended Payload Length)。

總結起來(lái),Payload length = 0 ~ 125,Extended Payload Length 不存在, 0 字節;Payload length = 126, Extended Payload Length 占 2 字節;Payload length = 127 時(shí),Extended Payload Length 占 8 字節。

另外需要注意的是,當 Payload length = 125 或 126 時(shí)接下(xià)來(lái)存儲實際包長的 2 字節或 8 字節,其值必須轉換為(wèi)網絡字節序(Big Endian)。

  • Masking-key ,如(rú)果前面的 mask 标志設置成 1,則該字段存在,占 4 個(gè)字節;反之,則 Frame 中不存在存儲 masking-key 字段的字節。

網絡上(shàng)一(yī)些(xiē)資料說(shuō),客戶端(主動發起握手請求的一(yī)方)給服務(wù)器(qì)(被動接受握手的另一(yī)方)發的 frame 信息(包信息),mask 标志必須是 1,而服務(wù)器(qì)給客戶端發送的 frame 信息中 mask 标志是 0。因此,客戶端發給服務(wù)器(qì)端的數據幀中存在 4 字節的 masking-key,而服務(wù)器(qì)端發給客戶端的數據幀中不存在 masking-key 信息。

我在 Websocket 協議(yì)的 RFC 文檔中并沒有看到有這(zhè)種強行規定,另外在研究了(le)一(yī)些(xiē) websocket 庫的實現(xiàn)後發現(xiàn),此結論并不一(yī)定成立,客戶端發送的數據也(yě)可能(néng)沒有設置 mask 标志。

如(rú)果存在 masking-key 信息,則數據幀中的數據(圖中 Payload Data)都是經過與 masking-key 進行運算(suàn)後的内容。無論是将原始數據與 masking-key 運算(suàn)後得到傳輸的數據,還是将傳輸的數據還原成原始數據,其算(suàn)法都是一(yī)樣的。算(suàn)法如(rú)下(xià):

1假設:
2original-octet-i:為(wèi)原始數據的第 i 字節。
3transformed-octet-i:為(wèi)轉換後的數據的第 i 字節。
4j:為(wèi)i mod 4的結果。
5masking-key-octet-j:為(wèi) mask key 第 j 字節。

算(suàn)法描述為(wèi):original-octet-i 與 masking-key-octet-j 異或後,得到 transformed-octet-i。

1  j  = i MOD 4
2transformed-octet-i = original-octet-i XOR masking-key-octet-j

我用 C++ 實現(xiàn)了(le)該算(suàn)法:

 1  /**
2   * @param src 函數調用前是原始需要傳輸的數據,函數調用後是mask或者unmask後的内容
3   * @param maskingKey 四字節
4   */

5  void maskAndUnmaskData(std::string& src, const char* maskingKey)
6  
{
7      char j;
8      for (size_t n = 0; n < src.length(); ++n)
9      {
10          j = n % 4;
11          src[n] = src[n] ^ maskingKey[j];
12      }
13  }

使用上(shàng)面的描述可能(néng)還不是太清楚,我們舉個(gè)例子(zǐ),假設有一(yī)個(gè)客戶端發送給服務(wù)器(qì)的數據包,那麽 mask = 1,即存在 4 字節的 masking-key,當包體(tǐ)數據長度在 0 ~ 125 之間(jiān)時(shí),該包的結構:

1第 1 個(gè)字節第 0 位    => FIN
2第 1 個(gè)字節第 1 ~ 3位 => RSV1 + RSV2 + RSV3
3第 1 個(gè)字節第 4 ~ 7位 => opcode
4第 2 個(gè)字節第 0 位    => mask(等于 1)
5第 2 個(gè)字節第 1 ~ 7位 => 包體(tǐ)長度
6第 3 ~ 6 個(gè)字節      =>  masking-key
7第 7 個(gè)字節及以後     =>  包體(tǐ)内容

這(zhè)種情形,包頭總共 6 個(gè)字節。

當包體(tǐ)數據長度大于125 且小于等于 UINT16_MAX 時(shí),該包的結構:

1第 1 個(gè)字節第 0 位    => FIN
2第 1 個(gè)字節第 1 ~ 3位 => RSV1 + RSV2 + RSV3
3第 1 個(gè)字節第 4 ~ 7位 => opcode
4第 2 個(gè)字節第 0 位    => mask(等于 1)
5第 2 個(gè)字節第 1 ~ 7位 => 開啓擴展包頭長度标志,值為(wèi) 126
6第 3 ~ 4 個(gè)字節      =>  包頭長度
7第 5 ~ 8 個(gè)字節      =>  masking-key
8第 9 個(gè)字節及以後     =>  包體(tǐ)内容

這(zhè)種情形,包頭總共 8 個(gè)字節。

當包體(tǐ)數據長度大于 UINT16_MAX 時(shí),該包的結構:

1第 1 個(gè)字節第 0 位    => FIN
2第 1 個(gè)字節第 1 ~ 3位 => RSV1 + RSV2 + RSV3
3第 1 個(gè)字節第 4 ~ 7位 => opcode
4第 2 個(gè)字節第 0 位    => mask(等于 1)
5第 2 個(gè)字節第 1 ~ 7位 => 開啓擴展包頭長度标志,值為(wèi) 127
6第 3 ~ 10 個(gè)字節      =>  包頭長度
7第 11 ~ 14 個(gè)字節     =>  masking-key
8第 15 個(gè)字節及以後     =>  包體(tǐ)内容

這(zhè)種情形,包頭總共 14 個(gè)字節。由于存儲包體(tǐ)長度使用 8 字節存儲(無符号),因此最大包體(tǐ)長度是 0xFFFFFFFFFFFFFFFF,這(zhè)是一(yī)個(gè)非常大的數字,但(dàn)實際開發中,我們用不到這(zhè)麽長的包體(tǐ),且當包體(tǐ)超過一(yī)定值時(shí),我們就(jiù)應該分(fēn)包(分(fēn)片)了(le)。

分(fēn)包的邏輯經過前面的分(fēn)析也(yě)很簡單,假設将一(yī)個(gè)包分(fēn)成 3 片,那麽應将第一(yī)個(gè)和第二個(gè)包片的第一(yī)個(gè)字節的第一(yī)位 FIN 設置為(wèi) 0,OpCode 設置為(wèi) CONTINUATION_FRAME(也(yě)是 0);第三個(gè)包片 FIN 設置為(wèi) 1,表示該包至此就(jiù)結束了(le),OpCode 設置為(wèi)想要的類型(如(rú) TEXT_FRAME、BINARY_FRAME 等)。對端收到該包時(shí),如(rú)果發現(xiàn)标志 FIN = 0 或 OpCode = 0,将該包包體(tǐ)的數據暫存起來(lái),直到收到 FIN = 1,OpCode ≠ 0 的包,将該包的數據與前面收到的數據放(fàng)在一(yī)起,組裝成一(yī)個(gè)完整的業務(wù)數據。示例代碼如(rú)下(xià):

 1//某次解包後得到包體(tǐ) payloadData,根據 FIN 标志判斷,
2//如(rú)果 FIN = true,則說(shuō)明一(yī)個(gè)完整的業務(wù)數據包已經收完整,
3//調用 processPackage() 函數處理(lǐ)該業務(wù)數據
4//否則,暫存于 m_strParsedData 中
5//每次處理(lǐ)完一(yī)個(gè)完整的業務(wù)包數據,即将暫存區m_strParsedData中的數據清空
6if (FIN)
7{
8    m_strParsedData.append(payloadData);
9    processPackage(m_strParsedData);
10    m_strParsedData.clear();
11}
12else
13{
14    m_strParsedData.append(payloadData);
15}

WebSocket 壓縮格式

WebSocket 對于包體(tǐ)也(yě)支持壓縮的,是否需要開啓壓縮需要通信雙方在握手時(shí)進行協商(shāng)。讓我們再看一(yī)下(xià)握手時(shí)主動發起一(yī)方的包内容:

 1GET /realtime HTTP/1.1\r\n
2Host: 127.0.0.1:9989\r\n
3Connection: Upgrade\r\n
4Pragma: no-cache\r\n
5Cache-Control: no-cache\r\n
6User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n
7Upgrade: websocket\r\n
8Origin: http://xyz.com\r\n
9Sec-WebSocket-Version: 13\r\n
10Accept-Encoding: gzip, deflate, br\r\n
11Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
12Sec-WebSocket-Key: IqcAWodjyPDJuhGgZwkpKg==\r\n
13Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n
14\r\n

在該包中 Sec-WebSocket-Extensions 字段中有一(yī)個(gè)值 permessage-deflate,如(rú)果發起方支持壓縮,在發起握手時(shí)将包中帶有該标志,對端收到後,如(rú)果也(yě)支持壓縮,則在應答(dá)的包也(yě)帶有該字段,反之不帶該标志即表示不支持壓縮。例如(rú):

1HTTP/1.1 101 Switching Protocols\r\n
2Upgrade: websocket\r\n
3Connection: Upgrade\r\n
4Sec-WebSocket-Accept: 5wC5L6joP6tl31zpj9OlCNv9Jy4=\r\n
5Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover
6\r\n

如(rú)果雙方都支持壓縮,此後通信的包中的包體(tǐ)部分(fēn)都是經過壓縮後的,反之是未壓縮過的。在解完包得到包體(tǐ)(即 Payload Data) 後,如(rú)果有握手時(shí)有壓縮标志并且乙方也(yě)回複了(le)支持壓縮,則需要對該包體(tǐ)進行解壓;同理(lǐ),在發數據組裝 WebSocket 包時(shí),需要先将包體(tǐ)(即 Payload Data)進行壓縮。

收到包需要解壓示例代碼:

 1bool MyWebSocketSession::processPackage(const std::string& data)
2{
3    std::string out;
4    //m_bClientCompressed在握手确定是否支持壓縮
5    if (m_bClientCompressed)
6    {
7        //解壓
8        if (!ZlibUtil::inflate(dataout))
9        {
10            LOGE("uncompress failed, dataLength: %d"data.length());
11            return false;
12        }
13
14    }
15    else
16        out = data;
17
18    //如(rú)果不需要解壓,則out=data,反之則out是解壓後的數據
19    LOGI("receid data: %s"out.c_str());
20
21
22    return Process(out);
23}

對包進行壓縮的算(suàn)法:

 1size_t dataLength = data.length();
2std::string destbuf;
3if (m_bClientCompressed)
4{
5    //按需壓縮
6    if (!ZlibUtil::deflate(data, destbuf))
7    {
8        LOGE("compress buf error, data: %s"data.c_str());
9        return;
10    }
11}
12else
13    destbuf = data;
14
15LOGI("destbuf.length(): %d", destbuf.length());    

壓縮和解壓算(suàn)法即 gzip 壓縮算(suàn)法。

由于公衆号文章最大是 5000 字數限制,本文原文一(yī)共有 12000 字,公衆号發文時(shí)有省略。如(rú)果想獲取完整的文章請在公衆号後台回複 關(guān)鍵字【websocket協議(yì)分(fēn)析】。獲取文中完整源碼,請在公衆号後台回複關(guān)鍵字【websocket源碼】。

最後,限于筆者經驗水平有限,歡迎讀者就(jiù)文中的觀點提出寶貴的建議(yì)和意見。



/template/Home/Zkeys/PC/Static