Go 实现简单websocket帧解析, 接发文本消息

websocket

这一篇继续上一篇文章的之后, 进行帧包的解析,当然是简洁的实现文本消息, 因为帧包里面的信息很多,就不一一弄了, 有兴趣的可以试

1
2
3
4
5
6
7
8
9
10
//定义一个64位的长度整形
var datalength int64
//定义个结构体
type webSocket struct {
Mask []byte
Conn net.Conn
}
func WebSocket(conn net.Conn) *webSocket {
return &webSocket{Conn:conn}
}

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/63)                 |
|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                     |
+----------------------------------------------------------------
 

下面一一解释包的说明
FIN 长度1位,表示是否是最后一帧,为1则表示最后一帧,为0则表示还有后续帧
RSV 长度三位 默认都是0 如果服务端与客户端没有协商,那么非0则认为是一个错误的帧
opcode 表示帧格式,占4位,格式如下

0x00,表示继续帧
0x01,表示文本帧
0x02,表示二进制帧
0x03-0x07,保留的未定义非控制帧
0x08,连接关闭帧
0x09,表示ping
0xA,表示pong
0xB-0xF,用于保留的控制帧

MASK,1位,定义负载数据是否使用掩码,1为使用掩码,0为不使用掩码

Payload Length,7位,7+16位,7+64位,定义负载数据的长度,以字节为单位。这部分如果为0-125,则负载长度则就是这段定义的长度,如果为126,之后的 Extend payload Length 16位将作为负载长度,如果为127,那么之后的Extend payload Length 64位将作为负载长度。

Masking-key 0 或者 32位,mask位设为0,则该字段缺失(不过协议要求,所有的帧都需要使用mask)

Payload data 负载数据=扩展数据+应用数据

包解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//读取
func (this *webSocket)readFrame() string {
//解析第一个字节位
first := make([]byte, 1)
this.Conn.Read(first)
//获取FIN值,0代表数据未结束 1 代表数据结束
FIN := first[0] >> 7
RSV1 := first[0] >> 6 & 1
RSV2 := first[0] >> 5 & 1
RSV3 := first[0] >> 4 & 1
log.Println(FIN, RSV1, RSV2, RSV3)
OPCODE := first[0] & 0xF
log.Println(OPCODE)
//解析第二个字节位
second := make([]byte, 1)
this.Conn.Read(second)
//获取MASK值
MASK := second[0] >> 7
log.Println(MASK)
payLength := second[0] & 0x7F
fmt.Println(int(payLength))
datalength = int64(payLength)

//如果payload 长度为126则读取后面的两个字节 数据长度
if payLength == 126 {
extendByte := make([]byte, 2)
this.Conn.Read(extendByte)
datalength = uint16(binary.BigEndian.Uint16(extendByte))
}
//如果payload 长度为127则读取后面的两个字节 数据长度
if payLength == 127 {
extendByte := make([]byte, 8)
this.Conn.Read(extendByte)
datalength = unit64(binary.BigEndian.Uint64(extendByte))
}

// 读取Masking-key
maskSec := make([]byte, 4)
if MASK == 1 {
this.Conn.Read(maskSec)
}
//读取数据
data := make([]byte, datalength)
this.Conn.Read(data)
if MASK == 1 {
var i int64
for i = 0; i < datalength; i++ {
data[i] ^= maskSec[i % 4]
}
}
fmt.Println(FIN)
// 如果FIN 为 1 表示是最后一个数据帧
if FIN == 1 {
return string(data)
}

getNextData := this.readFrame()

data = append(data, getNextData...)

return string(data)
}

//发送包
/**
给客户端发送数据, 也要以帧格式发送, 这里以不做掩码处理, 如果需要就自行改变帧的二进制格式
*/
func (this *webSocket) writeFrame(data []byte) {

length := len(data)
buf := make([]byte, 10+length)

// 数据开始和结束的位置
payloadStart := 2

// 数据帧的第一个字节, 不支持分片,且值能发送文本类型数据 二进制格式为 1000 0001
buf[0] = 0x81
// 数据帧第二个字节,服务器发送的数据不需要进行掩码处理
if length < 125 {
buf[1] = byte(0x00) | byte(length)
} else if (length > 125 && length < 65536) {
buf[1] = byte(0x00) | 126
binary.BigEndian.PutUint16(buf[payloadStart:], uint16(length))
payloadStart += 2
} else {
buf[1] = byte(0x00) | 127
binary.BigEndian.PutUint64(buf[payloadStart:], uint64(length))
payloadStart += 8
}
// 复制
copy(buf[payloadStart:], data)
this.Conn.Write(buf)
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package main

import (
"net"
"fmt"
"log"
"encoding/binary"
"strings"
"crypto/sha1"
"io"
"encoding/base64"
)

const (
WEBSOCKET_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
// 文本数据帧类型
)
var datalength int64

type webSocket struct {
Mask []byte
Conn net.Conn
}

func WebSocket(conn net.Conn) *webSocket {
return &webSocket{Conn:conn}
}
func main() {
connect()
}


func connect() {
ln, err := net.Listen("tcp", ":8000")

if err != nil {
fmt.Println(err)
}

for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
}

for {
handConnect(conn)
}
}
}

func handConnect(conn net.Conn) {
content := make([]byte, 1024)
n, err := conn.Read(content)
if err != nil {
fmt.Println(err)
}
fmt.Println(fmt.Sprintf("读取%d个字节", n))

header := parseHeaders(string(content))
fmt.Println(header["Sec-WebSocket-Key"])

secret := getSecret(header["Sec-WebSocket-Key"])

response := "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
response += "Upgrade: websocket\r\n"
response +="Connection: Upgrade\r\n"
response +="Sec-WebSocket-Accept: " + secret +"\r\n"
response += "\r\n"

conn.Write([]byte(response))

for {
ws := WebSocket(conn)
data := ws.readFrame()
fmt.Println("data is :", data)

ws.writeFrame([]byte("i recive a message as you konw"))
}

}

func (this *webSocket)readFrame() string {
//解析第一个字节位
first := make([]byte, 1)
this.Conn.Read(first)
//获取FIN值,0代表数据未结束 1 代表数据结束
FIN := first[0] >> 7
RSV1 := first[0] >> 6 & 1
RSV2 := first[0] >> 5 & 1
RSV3 := first[0] >> 4 & 1
log.Println(FIN, RSV1, RSV2, RSV3)
OPCODE := first[0] & 0xF
log.Println(OPCODE)
//解析第二个字节位
second := make([]byte, 1)
this.Conn.Read(second)
//获取MASK值
MASK := second[0] >> 7
log.Println(MASK)
payLength := second[0] & 0x7F
fmt.Println(int(payLength))
datalength = int64(payLength)

//如果payload 长度为126则读取后面的两个字节 数据长度
if payLength == 126 {
extendByte := make([]byte, 2)
this.Conn.Read(extendByte)
datalength = int64(binary.BigEndian.Uint16(extendByte))
}
//如果payload 长度为127则读取后面的两个字节 数据长度
if payLength == 127 {
extendByte := make([]byte, 8)
this.Conn.Read(extendByte)
datalength = int64(binary.BigEndian.Uint16(extendByte))
}

// 读取Masking-key
maskSec := make([]byte, 4)
if MASK == 1 {
this.Conn.Read(maskSec)
}
//读取数据
data := make([]byte, datalength)
this.Conn.Read(data)
if MASK == 1 {
var i int64
for i = 0; i < datalength; i++ {
data[i] ^= maskSec[i % 4]
}
}
fmt.Println(FIN)
// 如果FIN 为 1 表示是最后一个数据帧
if FIN == 1 {
return string(data)
}

getNextData := this.readFrame()

data = append(data, getNextData...)

return string(data)
}

/**
给客户端发送数据, 也要以帧格式发送, 这里以不做掩码处理, 如果需要就自行改变帧的二进制格式
*/
func (this *webSocket) writeFrame(data []byte) {

length := len(data)
buf := make([]byte, 10+length)

// 数据开始和结束的位置
payloadStart := 2

// 数据帧的第一个字节, 不支持分片,且值能发送文本类型数据 二进制格式为 1000 0001
buf[0] = 0x81
// 数据帧第二个字节,服务器发送的数据不需要进行掩码处理
if length < 125 {
buf[1] = byte(0x00) | byte(length)
} else if (length > 125 && length < 65536) {
buf[1] = byte(0x00) | 126
binary.BigEndian.PutUint16(buf[payloadStart:], uint16(length))
payloadStart += 2
} else {
buf[1] = byte(0x00) | 127
binary.BigEndian.PutUint64(buf[payloadStart:], uint64(length))
payloadStart += 8
}
// 复制
copy(buf[payloadStart:], data)
this.Conn.Write(buf)
}

func parseHeaders(content string) map[string]string {
h := strings.Split(content, "\r\n")
header := make(map[string]string, 0)
for _, value := range h {
v := strings.Split(value, ":")

if len(v) >= 2 {
header[strings.Trim(v[0], " ")] = strings.Trim(v[1], " ")
}
}

return header
}

func getSecret(key string) string{
key += WEBSOCKET_KEY

res := sha1.New()
io.WriteString(res, key)

return base64.StdEncoding.EncodeToString(res.Sum(nil))
}

试一试吧,前台