连接握手
每个WebSocket连接都始于一个HTTP请求,该请求包含特殊的首标Upgrade
、Sec-WebSocket-Key
。服务端响应101
代码、Connection
、Upgrade
、Sec-WebSocket-Accept
。
其中Sec-WebSocket-Accept
响应首标由Sec-WebSocket-Key
请求首标计算而来,包含特殊的响应键值。 这两组键值实际上是为了保护非WebSocket服务器,避免跨协议攻击。握手时要求服务端返回客户端期望收到的键值,如果客户端没有收到或与期望的键值不一致,服务端会返回“Error during WebSocket handshake: Sec-WebSocket-Accept mismatch”,然后客户端会关闭连接。那么Sec-WebSocket-Accept怎么计算得来呢?
- 在Sec-WebSocket-Key键值后添加258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID,全局唯一标识符)
- SHA1转换
- Base64编码
这样一来得到Sec-WebSocket-Accept并返回给客户端,客户端检查和期望的一致后,连接建立。 php中采用如下的方式获取
|
|
值得注意的是sha1()函数的第二个参数是可选的,默认为false,此时返回值是一个 40 字符长度的十六进制数字。在计算Sec-WebSocket-Accept的时候需要传入true,以 20 字符长度的原始格式返回。
数据帧
FIN:1 bit
为1表明这个是消息的最后片段。RSV1, RSV2, RSV3:各1 bit
双方协定自定义协议,否则这三位必须是 0。Opcode:4 bits
4位指定消息载荷类型的操作码。- %x0 代表一个继续帧
- %x1 代表一个文本帧
- %x2 代表一个二进制帧
- %x8 代表连接关闭
- %x9 代表 ping
- %xA 代表 pong
- %x3-7和%xB-F 保留用于未来的控制帧
ping和pong能够保持连接打开,为数据流动做好准备。一个 ping 即可以充当一个 keepalive,也可以作为验证远程端点仍可响应的手段。ping和pong可以从连接的任意一端发起,但大部分的ping和pong是由服务器端发起的。当收到一个ping时,接收端必须响应一个pong;一个pong也可以未经请求发送,用于单向的心跳。
Mask:1 bit
为1表明“负载数据”是掩码的,掩码键出现在 masking-key,用于解掩码 “负载数据”。从客户端发送到服务端的所有帧有这个位设置为 1。Payload length:7 bits, 7+16 bits, 或7+64 bits
“负载数据”的长度,以字节为单位:如果 0-125,这是负载长度。如果 126,之后的两字节解释为一个 16 位的无符号整数是负载长度。如果 127,之后的 8字节解释为一个 64 位的无符号整数(最高有效位必须是 0)是负载长度负载长度是“扩展数据”长度+“应用数据”长度。“扩展数据”长度可能是零,在这种情况下,负载长度是“应用数据”长度。Masking-key:0 或 4 bytes
客户端发送到服务端的所有帧通过一个包含在帧中的 32 位值来掩码。掩码键是由客户端随机选择的 32 位值。当准备一个掩码的帧时,客户端必须从允许的 32 位值集合中选择一个新的掩码键。
服务端收到掩码处理后的数据后,解码采用如下的算法。将Payload原始数据的每个字符的顺序下标模4,然后将此原始数据字符与掩码的模4相应位置的字符进行异或操作。这个算法对于加密和解密的操作都是一样的。
|
|
- Payload data:(x+y) bytes
“负载数据”定义为“扩展数据”和“应用数据”。如果没有定义扩展,则没有扩展数据,仅含有应用数据。
关闭握手
为了关闭WebSocket连接,一端必须发送一个关闭的控制帧,此时WebSocket 关闭阶段握手已启动, WebSocket 连接处于CLOSING状态。当两端都发送了关闭数据帧时,双方都要关闭所有的连接资源,当关闭之后,双方处于CLOSED状态。控制帧为一个“状态码”和一个“原因说明”,正常关闭的状态码为1000;如果close控制帧不包含状态码,close状态码被认为是1005;如果WebSocket连接已经关闭且端点没有接收到close状态码(例如可能发生在底层传输连接丢失时),close状态码被认为是1006。
参考文章
- The WebSocket Protocol RFC 6455
- RFC 6455译文
- Websocket协议数据帧传输和关闭连接(http://www.2cto.com/kf/201403/283799.html)
- 《HTML5 websocket 权威指南》