WebSocket 是一种网络通信协议,很多高级功能都需要它。
本文介绍 WebSocket 协议的实现及通信协议。
简介
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
Websocket既然是建立在TCP/IP协议之上的应用协议,那么就会存在数据传输的协议,而Websocket是借了Http协议的通道升级成一个长链接的Socket链接。
既然是和Socket连接一样,我们就去动手实现一下吧。提高提高我们对通信协议的理解及字节流的操作能力。
协议定义
协议地址rfc6455,协议直观图。从这张图中可以直观的看到每个字节中每个bit(位)所代表的含义。
FIN
标识是否为此消息的最后一个数据包,占 1 bit
RSV
RSV1, RSV2, RSV3: 用于扩展协议,一般为0,各占 1 bit
opcode
数据包类型(frame type),占4bits
0x0:标识一个中间数据包0x1:标识一个text类型数据包
0x2:标识一个binary类型数据包
0x3-7:保留
0x8:标识一个断开连接类型数据
0x9:标识一个ping类型数据包
0xA:表示一个pong类型数据包
0xB-F:保留
MASK
用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
Payload length
Payload data的长度,占7bits,7+16bits,7+64bits:
- 如果其值在0-125,则是payload的真实长度。
- 如果值是126,则后面2个字节形成的16bits无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
- 如果值是127,则后面8个字节形成的64bits无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
这里的长度表示遵循一个原则,用最少的字节表示长度(尽量减少不必要的传输)。举例说,payload真实长度是124,在0-125之间,必须用前7位表示;不允许长度1是126或127,然后长度2是124,这样违反原则。
Payload data
应用层数据
GO语言实现
当我们知道协议的定义之后,我们就可以一个字节一个字节的把数据读出来。然后按照协议定义中每个bit代表的含义解析出来就好了。
这其中解析字节中bit对应的值需要用到位移(>>,<<)和与(&)运算。原理也很简单,就是拿1左移到需要知道的bit的位置,然后进行&运算,就可以判断其bit值是0还是1。
1、先定义bit的位置
const (
// Frame header byte 0 bits from Section 5.2 of RFC 6455
finalBit = 1 << 7
rsv1Bit = 1 << 6
rsv2Bit = 1 << 5
rsv3Bit = 1 << 4
maskBit = 1 << 7
)
复制代码
2、然后读取前2个字节
var p = make([]byte, 2)
_, err := conn.Read(p)
if err != nil {
log.Fatalln("读取错误", err)
}
final := p[0]&finalBit != 0
rsv1 := p[0]&rsv1Bit != 0
rsv2 := p[0]&rsv2Bit != 0
rsv3 := p[0]&rsv3Bit != 0
opcode := p[0] & 0xf
复制代码
第一个字节的前4个bit对应的是fin、rsv1、rsv2、rsv3,后4个bit是opcode对应的是操作代码。后4个bit采用的是0xf 也就是15,二进制是1111与第一个bit进行与运算可以得到后4bit的真实值。至此我们第一个字节的内容就都解析出来了,得到了5个不同的数值定义。
第二个字节的第一个bit为masked,是否使用了掩码处理。如果为1,则需要进行解码处理。后7个bit为长度可以通过0x7f进行 & 运算得到这7位的真实值,二进制为1111111,数值为127
masked := p[1]&maskBit != 0
length := uint64(p[1] & 0x7f)
if length == 126 {
var lenBytes = make([]byte, 2)
reader.Read(lenBytes)
length = binary.BigEndian.Uint64(lenBytes)
}
if length == 127 {
var lenBytes = make([]byte, 8)
reader.Read(lenBytes)
length = binary.BigEndian.Uint64(lenBytes)
}
复制代码
通过以上代码,我们就解析了是否为掩码数据及数据长度了。
3、处理掩码key
在Payload域中,前4个数据是数据本身还是掩码域取决于切面解析的Masked是否为1。如果是掩码,还得将数据的4位读取出来作为解码key
//读取mask掩码
maskkey := make([]byte, 4)
if masked {
reader.Read(maskkey)
}
复制代码
4、读取Payload
万事具备,只剩下读取Payload了,读取长度为length。如果masked为1,就解码就是
//读取内容
payload := make([]byte, length)
reader.Read(payload)
if masked {
//解码
maskBytes(maskkey, 0, payload)
}
复制代码
完整代码
这里提供实例代码,仅供参考
package wsframe
import (
"encoding/binary"
"io"
"log"
"net"
"unsafe"
)
const (
// Frame header byte 0 bits from Section 5.2 of RFC 6455
finalBit = 1 << 7
rsv1Bit = 1 << 6
rsv2Bit = 1 << 5
rsv3Bit = 1 << 4
maskBit = 1 << 7
)
// The message types are defined in RFC 6455, section 11.8.
const (
// TextMessage denotes a text data message. The text message payload is
// interpreted as UTF-8 encoded text data.
TextMessage = 0x1
// BinaryMessage denotes a binary data message.
BinaryMessage = 0x2
// CloseMessage denotes a close control message. The optional message
// payload contains a numeric code and text. Use the FormatCloseMessage
// function to format a close message payload.
CloseMessage = 0x8
// PingMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PingMessage = 0x9
// PongMessage denotes a pong control message. The optional message payload
// is UTF-8 encoded text.
PongMessage = 0x10
)
const (
noFrame = -1
)
func ReadMessage(reader io.Reader) {
var p = make([]byte, 2)
_, err := reader.Read(p)
if err != nil {
log.Fatalln("读取错误", err)
}
log.Println("前2个byte值", p)
//通过将1进行左移一定的位数后与第一个字节进行与运算,可以判断该值是否为1
final := p[0]&finalBit != 0
rsv1 := p[0]&rsv1Bit != 0
rsv2 := p[0]&rsv2Bit != 0
rsv3 := p[0]&rsv3Bit != 0
//通过0xf=15来与后4位进行与运行,可以得到这4位原本的值
opcode := p[0] & 0xf
log.Println("第一个字节的值", final, rsv1, rsv2, rsv3, opcode)
//获取第2个字节的数据
masked := p[1]&maskBit != 0
length := uint64(p[1] & 0x7f)
if length == 126 {
var lenBytes = make([]byte, 2)
reader.Read(lenBytes)
length = binary.BigEndian.Uint64(lenBytes)
}
if length == 127 {
var lenBytes = make([]byte, 8)
reader.Read(lenBytes)
length = binary.BigEndian.Uint64(lenBytes)
}
log.Println("masked and length", masked, length)
switch opcode {
case CloseMessage, PingMessage, PongMessage:
log.Println("控制消息", opcode)
break
case TextMessage:
log.Println("文本消息", opcode)
break
case BinaryMessage:
log.Println("二进制消息", opcode)
break
}
//读取mask掩码
maskkey := make([]byte, 4)
if masked {
reader.Read(maskkey)
}
//读取内容
payload := make([]byte, length)
reader.Read(payload)
if masked {
//解码
maskBytes(maskkey, 0, payload)
}
log.Println("内容", string(payload))
}
const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key []byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers.
if len(b) < 2*wordSize {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
// Mask one byte at a time to word boundary.
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
n = wordSize - n
for i := range b[:n] {
b[i] ^= key[pos&3]
pos++
}
b = b[n:]
}
// Create aligned word size key.
var k [wordSize]byte
for i := range k {
k[i] = key[(pos+i)&3]
}
kw := *(*uintptr)(unsafe.Pointer(&k))
// Mask one word at a time.
n := (len(b) / wordSize) * wordSize
for i := 0; i < n; i += wordSize {
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
}
// Mask one byte at a time for remaining bytes.
b = b[n:]
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
复制代码