go的websocket实现

简介:

websocket分为握手和数据传输阶段,即进行了HTTP握手 + 双工的TCP连接

RFC协议文档在:http://tools.ietf.org/html/rfc6455

握手阶段

握手阶段就是普通的HTTP

客户端发送消息:

1
2
3
4
5
6
7
GET /chat HTTP/1.1
     Host: server.example.com
     Upgrade: websocket
     Connection: Upgrade
     Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
     Origin: http: //example.com
     Sec-WebSocket-Version: 13

服务端返回消息:

1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

这里的Sec-WebSocket-Accept的计算方法是:

base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

如果这个Sec-WebSocket-Accept计算错误浏览器会提示:

Sec-WebSocket-Accept dismatch

如果返回成功,Websocket就会回调onopen事件

数据传输

websocket的数据传输使用的协议是:

Image

参数的具体说明在这:

FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断;

RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;

Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码: 
      *  %x0 表示连续消息片断 
      *  %x1 表示文本消息片断 
      *  %x2 表未二进制消息片断 
      *  %x3-7 为将来的非控制消息片断保留的操作码 
      *  %x8 表示连接关闭 
      *  %x9 表示心跳检查的ping 
      *  %xA 表示心跳检查的pong 
      *  %xB-F 为将来的控制消息片断的保留操作码

Mask:1位,定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;

Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。

Masking-key:0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。 
Payload data: (x+y)位,负载数据为扩展数据及应用数据长度之和。 
Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。

Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。

参考自http://blog.csdn.net/fenglibing/article/details/6852497

实例

具体使用go的实现例子:

客户端:

html:

1
2
3
4
5
6
7
8
9
10
11
<html>
     <head>
         <script type= "text/javascript"  src= "./jquery.min.js" ></script>
     </head>
     <body>
         <input type= "button"  id= "connect"  value= "websocket connect"  />
         <input type= "button"  id= "send"  value= "websocket send"  />
         <input type= "button"  id= "close"  value= "websocket close"  />
     </body>
     <script type= "text/javascript"  src= "./websocket.js" ></script>
</html>

 

 

js:

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
var  socket;
 
$( "#connect" ).click( function (event){
     socket = new  WebSocket( "ws://127.0.0.1:8000" );
 
     socket.onopen = function (){
         alert( "Socket has been opened" );
     }
 
     socket.onmessage = function (msg){
         alert(msg.data);
     }
 
     socket.onclose = function () {
         alert( "Socket has been closed" );
     }
});
 
$( "#send" ).click( function (event){
     socket.send( "send from client" );
});
 
$( "#close" ).click( function (event){
     socket.close();
})

服务端:

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
197
198
199
200
201
202
203
package main
 
import(
     "net"
     "log"
     "strings"
     "crypto/sha1"
     "io"
     "encoding/base64"
     "errors"
)
 
func main() {
     ln, err := net.Listen( "tcp" , ":8000" )
     if  err != nil {
         log.Panic(err)
     }
 
     for  {
         conn, err := ln.Accept()
         if  err != nil {
             log.Println( "Accept err:" , err)
         }
         for  {
             handleConnection(conn)
         }
     }
}
 
func handleConnection(conn net.Conn) {
     content := make([]byte, 1024)
     _, err := conn.Read(content)
     log.Println(string(content))
     if  err != nil {
         log.Println(err)
     }
 
     isHttp := false
     // 先暂时这么判断
     if  string(content[0:3]) == "GET"  {
         isHttp = true;
     }
     log.Println( "isHttp:" , isHttp)
     if  isHttp {
         headers := parseHandshake(string(content))
         log.Println( "headers" , headers)
         secWebsocketKey := headers[ "Sec-WebSocket-Key" ]
 
         // NOTE:这里省略其他的验证
         guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 
         // 计算Sec-WebSocket-Accept
         h := sha1.New()
         log.Println( "accept raw:" , secWebsocketKey + guid)
 
         io.WriteString(h, secWebsocketKey + guid)
         accept := make([]byte, 28)
         base64.StdEncoding.Encode(accept, h.Sum(nil))
         log.Println(string(accept))
 
         response := "HTTP/1.1 101 Switching Protocols\r\n"
         response = response + "Sec-WebSocket-Accept: "  + string(accept) + "\r\n"
         response = response + "Connection: Upgrade\r\n"
         response = response + "Upgrade: websocket\r\n\r\n"
         
             
         log.Println( "response:" , response)
         if  lenth, err := conn.Write([]byte(response)); err != nil {
             log.Println(err)
         } else  {
             log.Println( "send len:" , lenth)
         }
 
         wssocket := NewWsSocket(conn)
         for  {
             data, err := wssocket.ReadIframe()
             if  err != nil {
                 log.Println( "readIframe err:"  , err)
             }
             log.Println( "read data:" , string(data))
             err = wssocket.SendIframe([]byte( "good" ))
             if  err != nil {
                 log.Println( "sendIframe err:"  , err)
             }
             log.Println( "send data" )
         }
         
     } else  {
         log.Println(string(content))
         // 直接读取
     }
}
 
type WsSocket struct {
     MaskingKey []byte
     Conn net.Conn
}
 
func NewWsSocket(conn net.Conn) *WsSocket {
     return  &WsSocket{Conn: conn}
}
 
func (this *WsSocket)SendIframe(data []byte) error {
     // 这里只处理data长度<125的
     if  len(data) >= 125 {
         return  errors.New( "send iframe data error" )
     }
 
     lenth := len(data)
     maskedData := make([]byte, lenth)
     for  i := 0; i < lenth; i++ {
         if  this.MaskingKey != nil {
             maskedData[i] = data[i] ^ this.MaskingKey[i % 4]
         } else  {
             maskedData[i] = data[i]
         }
     }
 
     this.Conn.Write([]byte{0x81})
 
     var  payLenByte byte
     if  this.MaskingKey != nil && len(this.MaskingKey) != 4 {
         payLenByte = byte(0x80) | byte(lenth)
         this.Conn.Write([]byte{payLenByte})
         this.Conn.Write(this.MaskingKey)
     } else  {
         payLenByte = byte(0x00) | byte(lenth)
         this.Conn.Write([]byte{payLenByte})
     }
     this.Conn.Write(data)
     return  nil
}
 
func (this *WsSocket)ReadIframe() (data []byte, err error){
     err = nil
 
     //第一个字节:FIN + RSV1-3 + OPCODE
     opcodeByte := make([]byte, 1)
     this.Conn.Read(opcodeByte)
 
     FIN := opcodeByte[0] >> 7
     RSV1 := opcodeByte[0] >> 6 & 1
     RSV2 := opcodeByte[0] >> 5 & 1
     RSV3 := opcodeByte[0] >> 4 & 1
     OPCODE := opcodeByte[0] & 15
     log.Println(RSV1,RSV2,RSV3,OPCODE)
 
     payloadLenByte := make([]byte, 1)
     this.Conn.Read(payloadLenByte)
     payloadLen := int(payloadLenByte[0] & 0x7F)
     mask := payloadLenByte[0] >> 7
 
     if  payloadLen == 127 {
         extendedByte := make([]byte, 8)
         this.Conn.Read(extendedByte)
     }
     
     maskingByte := make([]byte, 4)
     if  mask == 1 {
         this.Conn.Read(maskingByte)
         this.MaskingKey = maskingByte
     }
 
     payloadDataByte := make([]byte, payloadLen)
     this.Conn.Read(payloadDataByte)
     log.Println( "data:" , payloadDataByte)
 
     dataByte := make([]byte, payloadLen)
     for  i := 0; i < payloadLen; i++ {
         if  mask == 1 {
             dataByte[i] = payloadDataByte[i] ^ maskingByte[i % 4]
         } else  {
             dataByte[i] = payloadDataByte[i]
         }
     }
 
     if  FIN == 1 {
         data = dataByte
         return
     }
 
     nextData, err := this.ReadIframe()
     if  err != nil {
         return
     }
     data = append(data, nextData…)
     return
}
 
func parseHandshake(content string) map[string]string {
     headers := make(map[string]string, 10)
     lines := strings.Split(content, "\r\n" )
 
     for  _,line := range lines {
         if  len(line) >= 0 {
             words := strings.Split(line, ":" )
             if  len(words) == 2 {
                 headers[strings.Trim(words[0], " " )] = strings.Trim(words[1], " " )
             }
         }
     }
     return  headers
}

 

后话

PS:后来发现官方也有实现了websocket,只是它不是在pkg下,而是在net的branch下

强烈建议使用官方的websocket,不要自己写

https://code.google.com/p/go.net/

当然如果自己实现了一遍协议,看官方的包自然会更清晰了。

目录
相关文章
|
7月前
|
XML JSON Go
Swoole与Go系列教程之WebSocket服务的应用
在 WebSocket 协议出现之前,Web 应用为了能过获取到实时的数据都是通过不断轮询服务端的接口。轮询的效率、延时很低,并且很耗费资源。
1073 2
Swoole与Go系列教程之WebSocket服务的应用
|
6月前
|
前端开发 Go 开发者
用 Go + WebSocket 快速实现一个 chat 服务
用 Go + WebSocket 快速实现一个 chat 服务
|
6月前
|
网络协议 前端开发 Go
[go笔记]websocket入门
[go笔记]websocket入门
|
6月前
|
网络协议 Go
|
搜索推荐 Go 微服务
Go 语言怎么一键生成一个 gRPC 服务?
Go 语言怎么一键生成一个 gRPC 服务?
79 0
|
9月前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 与 Socket.IO 集成
【4月更文挑战第30天】本文介绍了在 Go 语言中集成 WebSocket 与 Socket.IO 的相关技术,WebSocket 是一种高效的双向通信协议,Socket.IO 是一个实时通信库,提供丰富的事件处理。集成两者能实现更强大的实时通信功能。文章讨论了 Go 中 WebSocket 的实现,Socket.IO 与 WebSocket 的关系,集成的意义及步骤,并提醒注意协议兼容性、消息格式等问题。此外,还提到了性能优化策略和应用案例,如实时聊天、数据监控和在线协作工具。通过集成,开发者可以构建出满足多样化需求的实时通信应用。
370 0
|
9月前
|
Go
简单的 Go gRPC 例子
简单的 Go gRPC 例子
88 0
|
安全 Go 网络安全
go-grpc的使用和学习
go-grpc的使用和学习
|
Go
go实现grpc通信
go实现grpc通信
123 0
go实现grpc通信
|
Web App开发 网络协议 测试技术
Go 实现 WebSockets:2. 如何在 Go 中创建 WebSockets 应用程序
上一篇文章我们先介绍了什么是 WebSockets 协议。本篇文章将来介绍如何利用 Go 来实现一个 WebSockets。要基于 net/http 库编写一个简单的 WebSocket 响应服务器,我们需要: 1.建立握手 2.从客户端接收数据帧 3.向客户端发送数据帧 4.关闭握手
Go 实现 WebSockets:2. 如何在 Go 中创建 WebSockets 应用程序