websocket分為握手和資料傳輸階段,即進行了http握手 + 雙工的tcp連接配接
握手階段就是普通的http
用戶端發送消息:
1
2
3
4
5
6
7
<code>get /chat http/1.1</code>
<code> </code><code>host: server.example.com</code>
<code> </code><code>upgrade: websocket</code>
<code> </code><code>connection: upgrade</code>
<code> </code><code>sec-websocket-key: dghlihnhbxbszsbub25jzq==</code>
<code> </code><code>origin: http:</code><code>//example.com</code>
<code> </code><code>sec-websocket-version: 13</code>
服務端傳回消息:
<code>http/1.1 101 switching protocols</code>
<code>upgrade: websocket</code>
<code>connection: upgrade</code>
<code>sec-websocket-accept: s3pplmbitxaq9kygzzhzrbk+xoo=</code>
這裡的sec-websocket-accept的計算方法是:
base64(hsa1(sec-websocket-key + 258eafa5-e914-47da-95ca-c5ab0dc85b11))
如果這個sec-websocket-accept計算錯誤浏覽器會提示:
sec-websocket-accept dismatch
如果傳回成功,websocket就會回調onopen事件
websocket的資料傳輸使用的協定是:

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位,任意的應用資料,放在擴充資料之後,應用資料的長度=負載資料的長度-擴充資料的長度。
具體使用go的實作例子:
html:
8
9
10
11
<code><html></code>
<code> </code><code><head></code>
<code> </code><code><script type=</code><code>"text/javascript"</code> <code>src=</code><code>"./jquery.min.js"</code><code>></script></code>
<code> </code><code></head></code>
<code> </code><code><body></code>
<code> </code><code><input type=</code><code>"button"</code> <code>id=</code><code>"connect"</code> <code>value=</code><code>"websocket connect"</code> <code>/></code>
<code> </code><code><input type=</code><code>"button"</code> <code>id=</code><code>"send"</code> <code>value=</code><code>"websocket send"</code> <code>/></code>
<code> </code><code><input type=</code><code>"button"</code> <code>id=</code><code>"close"</code> <code>value=</code><code>"websocket close"</code> <code>/></code>
<code> </code><code></body></code>
<code> </code><code><script type=</code><code>"text/javascript"</code> <code>src=</code><code>"./websocket.js"</code><code>></script></code>
<code></html></code>
js:
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<code>var</code> <code>socket;</code>
<code>$(</code><code>"#connect"</code><code>).click(</code><code>function</code><code>(event){</code>
<code> </code><code>socket.onopen =</code><code>function</code><code>(){</code>
<code> </code><code>alert(</code><code>"socket has been opened"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>socket.onmessage =</code><code>function</code><code>(msg){</code>
<code> </code><code>alert(msg.data);</code>
<code> </code><code>socket.onclose =</code><code>function</code><code>() {</code>
<code> </code><code>alert(</code><code>"socket has been closed"</code><code>);</code>
<code>});</code>
<code>$(</code><code>"#send"</code><code>).click(</code><code>function</code><code>(event){</code>
<code> </code><code>socket.send(</code><code>"send from client"</code><code>);</code>
<code>$(</code><code>"#close"</code><code>).click(</code><code>function</code><code>(event){</code>
<code> </code><code>socket.close();</code>
<code>})</code>
服務端:
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
<code>package main</code>
<code>import(</code>
<code> </code><code>"net"</code>
<code> </code><code>"log"</code>
<code> </code><code>"strings"</code>
<code> </code><code>"crypto/sha1"</code>
<code> </code><code>"io"</code>
<code> </code><code>"encoding/base64"</code>
<code> </code><code>"errors"</code>
<code>)</code>
<code>func main() {</code>
<code> </code><code>ln, err := net.listen(</code><code>"tcp"</code><code>,</code><code>":8000"</code><code>)</code>
<code> </code><code>if</code> <code>err != nil {</code>
<code> </code><code>log.panic(err)</code>
<code> </code><code>for</code> <code>{</code>
<code> </code><code>conn, err := ln.accept()</code>
<code> </code><code>if</code> <code>err != nil {</code>
<code> </code><code>log.println(</code><code>"accept err:"</code><code>, err)</code>
<code> </code><code>}</code>
<code> </code><code>for</code> <code>{</code>
<code> </code><code>handleconnection(conn)</code>
<code>}</code>
<code>func handleconnection(conn net.conn) {</code>
<code> </code><code>content := make([]byte, 1024)</code>
<code> </code><code>_, err := conn.read(content)</code>
<code> </code><code>log.println(string(content))</code>
<code> </code><code>log.println(err)</code>
<code> </code><code>ishttp := false</code>
<code> </code><code>// 先暫時這麼判斷</code>
<code> </code><code>if</code> <code>string(content[0:3]) ==</code><code>"get"</code> <code>{</code>
<code> </code><code>ishttp = true;</code>
<code> </code><code>log.println(</code><code>"ishttp:"</code><code>, ishttp)</code>
<code> </code><code>if</code> <code>ishttp {</code>
<code> </code><code>headers := parsehandshake(string(content))</code>
<code> </code><code>log.println(</code><code>"headers"</code><code>, headers)</code>
<code> </code><code>secwebsocketkey := headers[</code><code>"sec-websocket-key"</code><code>]</code>
<code> </code><code>// note:這裡省略其他的驗證</code>
<code> </code><code>guid :=</code><code>"258eafa5-e914-47da-95ca-c5ab0dc85b11"</code>
<code> </code><code>// 計算sec-websocket-accept</code>
<code> </code><code>h := sha1.new()</code>
<code> </code><code>log.println(</code><code>"accept raw:"</code><code>, secwebsocketkey + guid)</code>
<code> </code><code>io.writestring(h, secwebsocketkey + guid)</code>
<code> </code><code>accept := make([]byte, 28)</code>
<code> </code><code>base64.stdencoding.encode(accept, h.sum(nil))</code>
<code> </code><code>log.println(string(accept))</code>
<code> </code><code>response :=</code><code>"http/1.1 101 switching protocols\r\n"</code>
<code> </code><code>response = response +</code><code>"sec-websocket-accept: "</code> <code>+ string(accept) +</code><code>"\r\n"</code>
<code> </code><code>response = response +</code><code>"connection: upgrade\r\n"</code>
<code> </code><code>response = response +</code><code>"upgrade: websocket\r\n\r\n"</code>
<code> </code>
<code> </code>
<code> </code><code>log.println(</code><code>"response:"</code><code>, response)</code>
<code> </code><code>if</code> <code>lenth, err := conn.write([]byte(response)); err != nil {</code>
<code> </code><code>log.println(err)</code>
<code> </code><code>}</code><code>else</code> <code>{</code>
<code> </code><code>log.println(</code><code>"send len:"</code><code>, lenth)</code>
<code> </code><code>wssocket := newwssocket(conn)</code>
<code> </code><code>data, err := wssocket.readiframe()</code>
<code> </code><code>if</code> <code>err != nil {</code>
<code> </code><code>log.println(</code><code>"readiframe err:"</code> <code>, err)</code>
<code> </code><code>}</code>
<code> </code><code>log.println(</code><code>"read data:"</code><code>, string(data))</code>
<code> </code><code>err = wssocket.sendiframe([]byte(</code><code>"good"</code><code>))</code>
<code> </code><code>log.println(</code><code>"sendiframe err:"</code> <code>, err)</code>
<code> </code><code>log.println(</code><code>"send data"</code><code>)</code>
<code> </code><code>}</code><code>else</code> <code>{</code>
<code> </code><code>log.println(string(content))</code>
<code> </code><code>// 直接讀取</code>
<code>type wssocket struct {</code>
<code> </code><code>maskingkey []byte</code>
<code> </code><code>conn net.conn</code>
<code>func newwssocket(conn net.conn) *wssocket {</code>
<code> </code><code>return</code> <code>&wssocket{conn: conn}</code>
<code>func (this *wssocket)sendiframe(data []byte) error {</code>
<code> </code><code>// 這裡隻處理data長度<125的</code>
<code> </code><code>if</code> <code>len(data) >= 125 {</code>
<code> </code><code>return</code> <code>errors.new(</code><code>"send iframe data error"</code><code>)</code>
<code> </code><code>lenth := len(data)</code>
<code> </code><code>maskeddata := make([]byte, lenth)</code>
<code> </code><code>for</code> <code>i := 0; i < lenth; i++ {</code>
<code> </code><code>if</code> <code>this.maskingkey != nil {</code>
<code> </code><code>maskeddata[i] = data[i] ^ this.maskingkey[i % 4]</code>
<code> </code><code>maskeddata[i] = data[i]</code>
<code> </code><code>this.conn.write([]byte{0x81})</code>
<code> </code><code>var</code> <code>paylenbyte byte</code>
<code> </code><code>if</code> <code>this.maskingkey != nil && len(this.maskingkey) != 4 {</code>
<code> </code><code>paylenbyte = byte(0x80) | byte(lenth)</code>
<code> </code><code>this.conn.write([]byte{paylenbyte})</code>
<code> </code><code>this.conn.write(this.maskingkey)</code>
<code> </code><code>paylenbyte = byte(0x00) | byte(lenth)</code>
<code> </code><code>this.conn.write(data)</code>
<code> </code><code>return</code> <code>nil</code>
<code>func (this *wssocket)readiframe() (data []byte, err error){</code>
<code> </code><code>err = nil</code>
<code> </code><code>//第一個位元組:fin + rsv1-3 + opcode</code>
<code> </code><code>opcodebyte := make([]byte, 1)</code>
<code> </code><code>this.conn.read(opcodebyte)</code>
<code> </code><code>fin := opcodebyte[0] >> 7</code>
<code> </code><code>rsv1 := opcodebyte[0] >> 6 & 1</code>
<code> </code><code>rsv2 := opcodebyte[0] >> 5 & 1</code>
<code> </code><code>rsv3 := opcodebyte[0] >> 4 & 1</code>
<code> </code><code>opcode := opcodebyte[0] & 15</code>
<code> </code><code>log.println(rsv1,rsv2,rsv3,opcode)</code>
<code> </code><code>payloadlenbyte := make([]byte, 1)</code>
<code> </code><code>this.conn.read(payloadlenbyte)</code>
<code> </code><code>payloadlen := int(payloadlenbyte[0] & 0x7f)</code>
<code> </code><code>mask := payloadlenbyte[0] >> 7</code>
<code> </code><code>if</code> <code>payloadlen == 127 {</code>
<code> </code><code>extendedbyte := make([]byte, 8)</code>
<code> </code><code>this.conn.read(extendedbyte)</code>
<code> </code>
<code> </code><code>maskingbyte := make([]byte, 4)</code>
<code> </code><code>if</code> <code>mask == 1 {</code>
<code> </code><code>this.conn.read(maskingbyte)</code>
<code> </code><code>this.maskingkey = maskingbyte</code>
<code> </code><code>payloaddatabyte := make([]byte, payloadlen)</code>
<code> </code><code>this.conn.read(payloaddatabyte)</code>
<code> </code><code>log.println(</code><code>"data:"</code><code>, payloaddatabyte)</code>
<code> </code><code>databyte := make([]byte, payloadlen)</code>
<code> </code><code>for</code> <code>i := 0; i < payloadlen; i++ {</code>
<code> </code><code>if</code> <code>mask == 1 {</code>
<code> </code><code>databyte[i] = payloaddatabyte[i] ^ maskingbyte[i % 4]</code>
<code> </code><code>databyte[i] = payloaddatabyte[i]</code>
<code> </code><code>if</code> <code>fin == 1 {</code>
<code> </code><code>data = databyte</code>
<code> </code><code>return</code>
<code> </code><code>nextdata, err := this.readiframe()</code>
<code> </code><code>data = append(data, nextdata…)</code>
<code> </code><code>return</code>
<code>func parsehandshake(content string) map[string]string {</code>
<code> </code><code>headers := make(map[string]string, 10)</code>
<code> </code><code>lines := strings.split(content,</code><code>"\r\n"</code><code>)</code>
<code> </code><code>for</code> <code>_,line := range lines {</code>
<code> </code><code>if</code> <code>len(line) >= 0 {</code>
<code> </code><code>words := strings.split(line,</code><code>":"</code><code>)</code>
<code> </code><code>if</code> <code>len(words) == 2 {</code>
<code> </code><code>headers[strings.trim(words[0],</code><code>" "</code><code>)] = strings.trim(words[1],</code><code>" "</code><code>)</code>
<code> </code><code>return</code> <code>headers</code>
ps:後來發現官方也有實作了websocket,隻是它不是在pkg下,而是在net的branch下
強烈建議使用官方的websocket,不要自己寫
https://code.google.com/p/go.net/
當然如果自己實作了一遍協定,看官方的包自然會更清晰了。