WebSocket 协议初探( 二 )

引用自 RFC 6455 - Base Framing Protocol 这个一章节
数据帧格式

  • FIN1bit 表明是消息的最后一个数据帧,数据帧有可能是第一个,同时也是最后一个
  • RSV1, RSV2, RSV3每个1 bit 一般都设置为0,如果客户端和服务端采用了拓展,那么就可以为非0值,非0值的含义由拓展来决定,如果收到了非0的值,而且未采用拓展协商,那么接收终端就应该断开WebSocket连接
  • 操作码4个bit 决定了后续的载荷数据(Payload data)的解析方式,如果收到了未知的操作码,那么接收终端就应该断开WebSocket连接,具体的操作码如下:
*%x0:表明了这个一个持续帧(continuation frame),当操作码为0时,说明使用了数据分片,该帧为数据分片中的一帧*%x1:表明了这是一个文本帧(text frame)*%x2:表明了这是一个二进制帧(binary frame)*%x3-7:保留操作码,用于后续定义的非控制帧further non-control frames)*%x8:表明了这是一个关闭操作码*%x9:表明了这是一个ping操作码*%xA:表明了这是一个pong操作码*%xB-F :保留操作码,用于后续定义的控制帧( further control frames)复制代码
  • Mask 4个bit
表明是否要对载荷数据(Payload data)进行掩码操作,如果被置为1,那么 Masking-key 会定义一个 32位,4个字节的值,用于对载荷数据(Payload data)的反掩码操作,具体掩码的算法在后续内容中进行说明
值得注意的是,只有在客户端向服务端发送数据时,Mask才为1,而服务端向客户端发送数据时不需要进行掩码操作
  • Masking-key 0 或者4个bit
客户端向服务端发送的数据都进行了掩码操作,客户端必须为发送的每一个frame选择新的掩码 (随机生成),要求是这个掩码无法被提供数据的终端应用(即客户端)预测 备注:掩码长度不计入载荷数据(Payload data)的长度
  • Payload length 7 bits 或 7+16 bits 或 7+64 bits 长度为0 - 125 : 那么它就是载荷(playload)的长度 长度为126 : 那么接下来的2个字节(16位无符号整型)就是载荷(playload)的长度 长度为127 : 那么接下来的8个字节(64位无符号整型)就是载荷(playload)的长度
另外,Payload length采用了网络字节序,也就是大端(big endian数据的高字节保存在内存的低地址中),大小端具体详情请看百度百科 大小端模式
  • Payload data(x+y) 字节 载荷数据包括了应用数据(x)和拓展数据(y) 应用数据 y 字节: 如果存在拓展数据的话,占据了拓展数据之后的帧位置 拓展数据 x字节: 除非客户端和服务端进行了协商,那么拓展数据应为0,否则拓展数据应当在握手过程中明确定义其长度,或者协商如何进行长度计算(如果存在拓展数据,那么它的长度应当也计入载荷长度中)
  • 掩码的算法
/**original-octet-i为原始数据的第i字节masking-key-octet-j为masking-key的第j个字节transformed-octet-i为掩码计算后的第i个字节*/Octet i of the transformed data ("transformed-octet-i") is the XOR ofoctet i of the original data ("original-octet-i") with octet at indexi modulo 4 of the masking key ("masking-key-octet-j"): j= i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j摘抄自 RFC : Client-to-Server Masking
也就是将原始数据和masking-key做异或操作(异或的位数 j = i mode 4),获得的就是转换后的结果
发送数据WebSocket根据 opcode 操作码来区分操作类型,%x0 %x1 %x2 代表了数据交互帧
  • %x0 : 表明了这个一个持续帧(continuation frame),当操作码为0时,说明使用了数据分片,该帧为数据分片中的一帧
  • %x1 :表明了这是一个文本帧(text frame)
  • %x2 :表明了这是一个二进制帧(binary frame
特别的操作码 %x0 代表使用了数据分片,消息可能被切分成多个数据帧,笔者发现 tornado 和 SocketRocket 并没有实现数据分片,这里就暂不深入讨论,详情请参考 RFC: Fragmentation
存活心跳ping 和 pong一旦建立与服务器的连接,客户端和服务端都可以发起一个ping请求,当接收到一个ping请求,那么接收端必须要尽快回复pong请求,通过这种方式,来确认对方是否存活,确保客户端、服务端之间的TCP通道保持连接没有断开
ping, pong操作码分别为 %x9 %xA
断开连接客户端和服务端都可以通过发送带有特殊控制序列 %x8 的数据帧来发起断开连接,一旦某一端收到该帧,需要也响应一个断开帧,之后主动断开的那端便可以关闭连接了,之前提到过Websocket 握手过程复用了HTTP协议的信道,那么很自然的,断开连接期间也经历了四次挥手的过程


推荐阅读