欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

网络协议:WebSocket协议的握手和数据帧

程序员文章站 2022-03-16 11:58:15
WebSocket是定义服务器和客户端如何通过Web通信的一种网络协议。协议是通信的议定规则。组成互联网的协议组由IETF(互联网工程任务组)发布。IETF发布评议请求(Request for...

WebSocket是定义服务器和客户端如何通过Web通信的一种网络协议。协议是通信的议定规则。组成互联网的协议组由IETF(互联网工程任务组)发布。IETF发布评议请求(Request for Comments,RFC),精确地规定了协议(包括RFC 6455):WebSocket协议。RFC 6455于2011年12月发布,包含了实现WebSocket客户端和服务器时必须遵循的规则。

websocket基本上是一个很简单的协议, 主要流程非常少, 实现起来也很简单。

为简单起见, 下面只分析握手和数据帧的报文.

一. 握手(handshake).

握手协议由客户端发起, 服务器响应, 一来一回就完成了. 基本上是为了兼容现有的http基础设施.

下面是一个客户端发起的握手请求:

47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET./.HTTP/1.1..  
55 70 67 72 61 64 65 3A 20 77 65 62 73 6F 63 6B Upgrade:.websock 
65 74 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 et..Connection:. 
55 70 67 72 61 64 65 0D 0A 48 6F 73 74 3A 20 31 Upgrade..Host:.1 
39 32 2E 31 36 38 2E 38 2E 31 32 38 3A 31 33 30 92.168.8.128:130 
30 0D 0A 4F 72 69 67 69 6E 3A 20 6E 75 6C 6C 0D 0..Origin:.null. 
0A 50 72 61 67 6D 61 3A 20 6E 6F 2D 63 61 63 68 .Pragma:.no-cach 
65 0D 0A 43 61 63 68 65 2D 43 6F 6E 74 72 6F 6C e..Cache-Control 
3A 20 6E 6F 2D 63 61 63 68 65 0D 0A 53 65 63 2D :.no-cache..Sec- 
57 65 62 53 6F 63 6B 65 74 2D 4B 65 79 3A 20 64 WebSocket-Key:.d 
33 35 39 46 64 6F 36 6F 6D 79 71 66 78 79 59 46 359Fdo6omyqfxyYF 
37 59 61 63 77 3D 3D 0D 0A 53 65 63 2D 57 65 62 7Yacw==..Sec-Web 
53 6F 63 6B 65 74 2D 56 65 72 73 69 6F 6E 3A 20 Socket-Version:. 
31 33 0D 0A 53 65 63 2D 57 65 62 53 6F 63 6B 65 13..Sec-WebSocke 
74 2D 45 78 74 65 6E 73 69 6F 6E 73 3A 20 78 2D t-Extensions:.x- 
77 65 62 6B 69 74 2D 64 65 66 6C 61 74 65 2D 66 webkit-deflate-f 
72 61 6D 65 0D 0A 55 73 65 72 2D 41 67 65 6E 74 rame..User-Agent 
3A 20 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 20 28 57 :.Mozilla/5.0.(W 
69 6E 64 6F 77 73 20 4E 54 20 36 2E 31 3B 20 57 indows.NT.6.1;.W 
4F 57 36 34 29 20 41 70 70 6C 65 57 65 62 4B 69 OW64).AppleWebKi 
74 2F 35 33 37 2E 33 36 20 28 4B 48 54 4D 4C 2C t/537.36.(KHTML, 
20 6C 69 6B 65 20 47 65 63 6B 6F 29 20 43 68 72 .like.Gecko).Chr 
6F 6D 65 2F 33 32 2E 30 2E 31 36 35 33 2E 30 20 ome/32.0.1653.0. 
53 61 66 61 72 69 2F 35 33 37 2E 33 36 0D 0A 0D Safari/537.36... 
0A  

0D 0A 0D 0A, 也就是用"\r\n\r\n"收尾, 这和http头没什么区别. 转换成字符串就是:

GET / HTTP/1.1  
Upgrade: websocket 
Connection: Upgrade 
Host: 192.168.8.128:1300 
Origin: null 
Pragma: no-cache 
Cache-Control: no-cache 
Sec-WebSocket-Key: d359Fdo6omyqfxyYF7Yacw== 
Sec-WebSocket-Version: 13 
Sec-WebSocket-Extensions: x-webkit-deflate-frame 
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1653.0 Safari/537.36 

其中有一对重要的kv, 就是Sec-WebSocket-Key: d359Fdo6omyqfxyYF7Yacw==, 看上去是一个base64编码后的结果, 服务器需要对这个sec-key作一些处理, 并返回握手响应, 这个处理是:

  1. byte[] sha = sha1(("d359Fdo6omyqfxyYF7Yacw==" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes());  
  2. System.out.println(new String(Base64.getEncoder().encode(sha)));  

    也就是原封不动的拿着这个sec-key和另一个神奇的字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"相连, 再经过sha1摘要

    算法处理, 最后再经过base64编码输出即可, 上面的输出结果应该是: pLO2KC7b5t0TZl1E6A3sqJ6EzU4=

    服务器在收到握手请求后, 如果愿意提供服务, 则返回一个握手响应, 如下:

    1. HTTP/1.1 101 Switching Protocols  
    2. Connection: Upgrade  
    3. Upgrade: WebSocket  
    4. Sec-WebSocket-Accept: pLO2KC7b5t0TZl1E6A3sqJ6EzU4=  

      遵循http的规则, 字节流上一样是要以"\r\n\r\n"收尾.

      二. 数据帧

      rfc6455上叫做非控制帧, 除了非控制帧之外, 就是控制帧. 包括connection close, ping, pong等帧, 这里只讲非控制帧, 也就是数据帧.

      数据帧从长度上可以分为三种. 帧中的静荷数据(payload data)长度小于0x7E的为小帧, 静荷数据长度 >=0x7E又<=0x10000的为中帧,

      再长的叫大帧.

      数据帧从类型上暂时可以分为两种, 文本帧和二进制帧.

      例子:

      a). 一个从客户端发向服务端的小帧.

      1. 82 B0 6A F7 C6 30 0A D9 C6 34 D4 18 78 C1 6E F5 ..j..0...4..x.n.  
      2. C6 30 6C D5 CC 10 23 87 AF 48 3C A2 9C 64 01 C4 .0l...#..H<..d..  
      3. AE 59 04 C5 B1 5B 35 85 A3 41 18 B0 F5 5C 13 8E .Y...[5..A...\..  
      4. 92 42 02 84 85 53                               .B...S  

        82

        二进制为: 1000 0010, 最高位(FIN)为1, 表示这是最后一帧, 第一个帧也可能是最后一帧. 身后还有三位为预留. 低位四0010为操作码.

        也就是0x02, 表示这是一个二进制帧, 0x01为文本帧.

        B0

        二进制为: 1011 0000, 最高位(MASK)为1, 表示当前帧的静荷数据部分使用了掩码, 事实上, rfc6455规定从客户端发往服务器端的数据帧

        必需使用掩码, 反过来, 从服务器发回来的, 则必需不使用掩码. 低7位为静荷数据长度字段, 这里是011 0000, 也就是0x30, 从上面的报文上

        看, 这个0x30没有包含后面的掩码.

        6A F7 C6 30

        掩码, 掩码总是四个字节.

        0A D9 C6...一直到最后为经过掩码加工后的静荷数据. 要回到数据本来的面目, 使用下面的算法:

        1. byte by[] = new byte[]{0x82, 0xB0, 0x6A, 0xF7, 0xC6, 0x30, 0x0A....};  
        2. byte mask[] = new byte[] { 0x6A, (byte) 0xF7, (byte) 0xC6, 0x30 };  
        3. for (int i = 6 /* 越过掩码. */; i < by.length; i++)  
        4.     by[i] = (byte) (by[i] ^ mask[(i - 6) % 4]);  

          得到的结果应该是:

          1. 82 B0 6A F7 C6 30 60 2E 00 04 BE EF BE F1 04 02 ..j..0`.........  
          2. 00 00 06 22 0A 20 49 70 69 78 56 55 5A 54 6B 33 ..."..IpixVUZTk3  
          3. 68 69 6E 32 77 6B 5F 72 65 71 72 47 33 6C 79 79 hin2wk_reqrG3lyy  
          4. 54 72 68 73 43 63                               TrhsCc  

            b). 一个从服务器发给客户端的小帧.

            1. 82 29 61 27 01 04 BE EF BE F1 05 02 00 00 06 1B .)a'............  
            2. 0A 08 55 3B 02 19 39 35 E2 44 12 0F 21 EC BC 47 ..U;..95.D..!..G  
            3. 02 F3 EC 70 ED 5B 7B 07 C7 F4 D0                ...p.[{....  

              更简单了, 还是82, 最后一帧, 二进制帧, 29, 0010 1001, 无掩码, 也就是身后全长为0x29.

              c). 未使用掩码的中帧.

              81 7E 01 00 66 77 88 ..., 帧长为 0x0100, 也就是256个字节.

              d). 未使用掩码的大帧.

              82 7F 00 00 00 00 11 22 33 44 66 77 88 ..., 帧长为0x0000000011223344, 直接跳过4字节, 而使用8字节来表示长度, 非常暴力.

              这里需要注意的是, websocket要求使用最小帧原则, 也就是静荷数据长度小于0x7E帧, 不能使用中帧或大帧的来表示. 长度小于

              0x10000的帧也不能用大帧来表示.

              【编辑推荐】

              网络协议X档案网络传输协议篇应用Wireshark观察基本网络协议移动网络性能揭秘--网络协议及性能提升实践【责任编辑:蓝雨泪 TEL:(010)68476606】