暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

webSocket那些事(三),协议详解

红牛编程 2017-06-17
698

写在前面

本文涉及:

  • WebSocket原理

  • 浏览器端语法

  • PHP实用WorkerMan实现WebSocket服务器端

  • socket.io

  • NodeJS下的WebSocket

  • 握手认证流程

  • WebSocket协议

本文是第三部分,主要详解websocket协议.

可以参考 本文的第一部分, 了解websocket的原理.

可以参考 webSocket那些事(二)之socket.io

输入 websocket 获取系列文章

5 webSocket协议

协议共分两个部分:
1.在正式交互之前先有一个握手的过程,握手是基于http的.握手的主要目的,升级协议和认证.
2.握手结束之后,进行webSocket进行数据,交互.

5.1 握手, Opening Handshake

(下面的头信息, 是基于, socket.io的案例获取的, 可以自行获取)
客户端发起连接Handshake请求, 请求头如下:

Host: localhost:8042
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: SgEboZ766nx/S6lK8ptnCw==
Connection: keep-alive, Upgrade
Upgrade: websocket
# 上面是关于握手的头信息部分; 下面是常规http请求头可以忽略
Origin: http://localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,zh-CN;q=0.8,ja;q=0.6,zh;q=0.4,en-US;q=0.2
Accept-Encoding: gzip, deflate
Pragma: no-cache
Cache-Control: no-cache

其中请求头信息的解释如下:

  • Upgrade:WebSocket, 请求将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议. 必要的.

  • Sec-WebSocket-Key, 浏览器端密钥,服务器端收到后用于加密。采用base64编码, 浏览器自由生成. 必要的.

  • Sec-WebSocket-Version: 13, webSocket版本号,目前浏览器端版本为13.

  • Sec-WebSocket-Extensions: permessage-deflate. 扩展操作, 支持消息压缩.

  • Connection: keep-alive, Upgrade, 连接方式升级.

服务器端接到请求后, 响应握手, 响应头如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hoAYS/0BEUVQ54O75UUPu5/T3Us=
Sec-WebSocket-Extensions: permessage-deflate

其中响应头信息解释如下:

  • HTTP/1.1 101 Switching Protocols, 状态码101, 表示成功转换到websocket协议, 非101都表示握手失败.

  • Sec-WebSocket-Accept: wwXNp/Ecsmg6T7Peu8enIxHpD+s=. 服务器端在接收到的请求头Sec-WebSocket-Key的密钥后, 追加掩码字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,并将结果进行sha-1哈希,然后再进行base64加密返回给客户端, 用于浏览器端认证服务器端。代码表述为:

    function generate()   
     // 使用上面的key  $key = 'SgEboZ766nx/S6lK8ptnCw==';  $mask = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';  // 生成  return base64_encode(sha1($key . $mask, true));  // 结果就是: hoAYS/0BEUVQ54O75UUPu5/T3Us=
    }

  • Connection: Upgrade, 连接方式升级.

  • Sec-WebSocket-Extensions: permessage-deflate, 消息加密处理, 与请求头一致.

总体来说,以上http请求响应,握手的过程就是:
浏览器对服务器说,我们要把协议升级,我浏览器的标识是一大串给定的key. 当服务器接收到浏览器请求是时, 想用101,告诉浏览器完成升级.并同时利用浏览器发送过来的key, 连接上固定的掩码字符串,响应给浏览器.

完成以上步骤,握手成功. 此时浏览器端和服务器端就保持住这个tcp连接,接下来的任务,就是基于该tcp连接,发送websocket协议格式数据.

5.2 websocket协议数据格式交

我们基于websocket协议发送的每一条消息,都被包装成一个独立的数据帧, 无论是浏览器向服务器还是服务器向浏览器.
在数据帧当中,除了包含我们发送的数据内容之外,还包含了数据长度,内容类型,是否掩码处理等信息.

例如, 一个服务器端向浏览器端发送的消息”Hello”, 浏览器接收到的数据如下:

‭   10000001000001010100100001100101011011000110110001101111‬
   (16进制HEX表示为: 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f)

其中, 每位的意思如下(从左侧开始表述):

位索引占用空间(bit)当前值释义
01bit1FIN, 标志是否为此消息的最后一个数据帧
1-3各1,共3bit000RSV1,RSV2,RSV3, 通常是 0,除非有扩展赋予了这些数位非 0 值的意义
4-74bit0001操作码opcode, 定义了如何解释消息内容(有效负荷数据 Payload data), 0001表示一个文本类型数据帧, 其他类型, 参见下操作码表
81bit0掩码标识, 0表示未掩码
9-157bit0000101有效负荷数据长度(Payload length), 字节单位, hello的长度为5. 如果消息很长, 会占用更长的空间, 可能是7, 7+16, 7+64 bit位
16-5540bit

0100100001100101011011000110110001101111‬

符合数据Payload Data,就是”Hello”文本编码, 参考ascii码表,可确定就是Hello文本.01001000(H) 01100101(e) 01101100(l) 01101100(l) 01101111(o)‬

操作码表:

  • %x0 表示这是一个继续帧(continuation frame)

  • %x1 表示这是一个文本帧 (text frame)

  • %x2 表示这是一个二进制帧 (binary frame)

  • %x3-7 为将来的非控制帧(non-control frame)而保留的

  • %x8 表示这是一个连接关闭帧 (connection close)

  • %x9 表示这是一个 ping 帧

  • %xA 表示这是一个 pong 帧

  • xB-F 为将来的控制帧(control frame)而保留的

从以上数据可以看到, 基于websocket的数据是被打包好的数据封包.
一个图例表示如下: (来源于网络)

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

可以看到, 我们给出的示例数据, 包含了上图的大部分结构.
其中为包含的有2处:

  • Extended payload length, 扩展负荷数据长度, 我们的数据很短. 因此常规长度即可表示.

  • Masking-key, 掩码key, 占用32bit(4字节), 由于示例数据是服务器向浏览器发送的”Hello, 不需要掩码处理, 因此没有此部分. 对掩码感兴趣, 可以继续向下.

以上就是一个websocket数据表封包(数据帧)的格式.

5.3 封包掩码的处理

如果是浏览器向服务器发送的封包, 主体格式与服务器封包一致,. 额外的websocket会掩码处理封包. 这是一个规范, 必须要掩码.

数据掩码的步骤:

  • 首先, 将掩码标志位设置为 1, 表示数据掩码处理.

  • 其次, 设置掩码mask-key. 掩码Key由客户端随机选取一个 32 位的值, 在每次准备对帧进行掩码操作时,客户端必须选择一个新的掩码Key进行处理.

  • 然后, 生成掩码传输.

生成的计算方式为:

通过原数据中的每8个bit位的数据i(original-octet-i) 与 以i和4 取模为索引的掩码Key中的某8位为j(masking-key-octet-j) 进行异或(XOR)操作, 得到传输数据中的每8个bit位的数据i (transformed-octet-i), 演示公式如下:
   j = i MOD 4
   transformed-octet-i = original-octet-i XOR masking-key-octet-j

如有需要, 可以自行演算.

5.4 小结

总的来说, 就是, 先通过http, 服务器端与浏览器端建立websocket连接. 之后浏览器与服务器一直保持通讯. 可以相互发送数据. 发送的每个数据被保证成数据帧(封闭), 包含了数据内容, 掩码等信息. 浏览器和服务器接收到对方发送的消息后, 进行拆包, 如是掩码数据, 则执行反掩码操作. 这样就可以完成通讯了.

6 总结

WebSocket 是一个独立的基于 TCP 的协议,它与 HTTP 之间的唯一关系就是它的握手请求可以作为一个升级请求(Upgrade request)经由 HTTP 服务器解释. 因此可以使用http服务器(nginx, apache), 作为websocket服务器的反向代理使用.

默认情况下,WebSocket 协议使用 80 端口作为一般请求的端口,端口 443 作为基于传输加密层连(TLS)RFC2818 接的端口. 与典型的HTTP协议保持一致.

再严谨一点:
websocket是一个网络通讯协议. 不属于html5. 仅仅是html5标准中实现了基于该协议的通讯.
因此, 只要理解上面的数据帧格式, 和 握手流程, 都可以完成基于websokect的即时通讯.


一家之言, 欢迎讨论拍砖!

文章转载自红牛编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论