写在前面
本文涉及:
WebSocket原理
浏览器端语法
PHP实用WorkerMan实现WebSocket服务器端
socket.io
NodeJS下的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) | 当前值 | 释义 |
|---|---|---|---|
| 0 | 1bit | 1 | FIN, 标志是否为此消息的最后一个数据帧 |
| 1-3 | 各1,共3bit | 000 | RSV1,RSV2,RSV3, 通常是 0,除非有扩展赋予了这些数位非 0 值的意义 |
| 4-7 | 4bit | 0001 | 操作码opcode, 定义了如何解释消息内容(有效负荷数据 Payload data), 0001表示一个文本类型数据帧, 其他类型, 参见下操作码表 |
| 8 | 1bit | 0 | 掩码标识, 0表示未掩码 |
| 9-15 | 7bit | 0000101 | 有效负荷数据长度(Payload length), 字节单位, hello的长度为5. 如果消息很长, 会占用更长的空间, 可能是7, 7+16, 7+64 bit位 |
| 16-55 | 40bit | 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的即时通讯.
一家之言, 欢迎讨论拍砖!




