天天看點

go的websocket實作握手階段資料傳輸執行個體後話

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的資料傳輸使用的協定是:

go的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>&lt;html&gt;</code>

<code>    </code><code>&lt;head&gt;</code>

<code>        </code><code>&lt;script type=</code><code>"text/javascript"</code> <code>src=</code><code>"./jquery.min.js"</code><code>&gt;&lt;/script&gt;</code>

<code>    </code><code>&lt;/head&gt;</code>

<code>    </code><code>&lt;body&gt;</code>

<code>        </code><code>&lt;input type=</code><code>"button"</code> <code>id=</code><code>"connect"</code> <code>value=</code><code>"websocket connect"</code> <code>/&gt;</code>

<code>        </code><code>&lt;input type=</code><code>"button"</code> <code>id=</code><code>"send"</code> <code>value=</code><code>"websocket send"</code> <code>/&gt;</code>

<code>        </code><code>&lt;input type=</code><code>"button"</code> <code>id=</code><code>"close"</code> <code>value=</code><code>"websocket close"</code> <code>/&gt;</code>

<code>    </code><code>&lt;/body&gt;</code>

<code>    </code><code>&lt;script type=</code><code>"text/javascript"</code> <code>src=</code><code>"./websocket.js"</code><code>&gt;&lt;/script&gt;</code>

<code>&lt;/html&gt;</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>&amp;wssocket{conn: conn}</code>

<code>func (this *wssocket)sendiframe(data []byte) error {</code>

<code>    </code><code>// 這裡隻處理data長度&lt;125的</code>

<code>    </code><code>if</code> <code>len(data) &gt;= 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 &lt; 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 &amp;&amp; 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] &gt;&gt; 7</code>

<code>    </code><code>rsv1 := opcodebyte[0] &gt;&gt; 6 &amp; 1</code>

<code>    </code><code>rsv2 := opcodebyte[0] &gt;&gt; 5 &amp; 1</code>

<code>    </code><code>rsv3 := opcodebyte[0] &gt;&gt; 4 &amp; 1</code>

<code>    </code><code>opcode := opcodebyte[0] &amp; 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] &amp; 0x7f)</code>

<code>    </code><code>mask := payloadlenbyte[0] &gt;&gt; 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 &lt; 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) &gt;= 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/

當然如果自己實作了一遍協定,看官方的包自然會更清晰了。

繼續閱讀