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的数据传输使用的协议是:

参数的具体说明在这:

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/

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

时间: 2024-11-02 03:06:38

go的websocket实现的相关文章

网上测试了很多关于PYTHON的WEBSOCKET样例,下面这个才成功了

这是最底层的, 嘿嘿,我 还是习惯搞个框架来实现急需要的功能... 这个东东玩得很有意思的.. 服务器端的代码: import simplejson import socket import sys import base64 import hashlib import time HOST = '127.0.0.1' PORT = 9000 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' HANDSHAKE_STRING = "HTTP

为什么我们需要HTML5 WebSocket

HTML5 WebSocket简介     HTML5作为下一代的 Web 标准,它拥有许多引人注目的新特性,如 Canvas.本地存储.多媒体编程接口.WebSocket等等.这其中有"Web 的 TCP "之称的WebSocket格外吸引开发人员的注意.WebSocket的出现使得浏览器提供对Socket的支持成为可能,从而在浏览器和服务器之间提供了一个基于TCP连接的双向通道.Web开发人员可以非常方便地使用WebSocket构建实时web应用,开发人员的手中从此又多了一柄神兵利

WebSocket 和 Golang 实现聊天功能

这个示例应用程序展示了如何使用 WebSocket, Golang 和 jQuery 创建一个简单的web聊天应用程序.这个示例的源代码在 https://github.com/waylau/goChat . Running the example 运行示例 这个示例需要 Golang 开发环境. 该页面描述如何安装开发环境. 一旦你去启动和运行,您可以下载.构建和运行的例子, 使用命令: go get gary.burd.info/go-websocket-chat go-websocket-

html5利用websocket完成的推送功能(tomcat)

插播一条消息,5天后会删掉的 本人东北大学软件学院大三学生,现在正在寻找实习,qq:1021842556 利用websocket和java完成的消息推送功能,服务器用的是tomcat7.0,一些东西是自己琢磨的,也不知道恰不恰当,不恰当处,还请各位见谅,并指出. 程序简单来说,就是客户A可以发送消息给客户B,但有很多可以扩展的地方, 比如 1.如果加入数据库后,A发消息时客户B未上线,服务端将会把消息存在数据库中,等客户B上线后,在将消息取出发送给客户B 2.服务端也可发送消息到任意客户端上.

.NET的WebSocket开发包比较

本文出现在第三方产品评论部分中.在这一部分的文章只提供给会员,不允许工具供应商用来以任何方式和形式来促销或宣传产品.请会员报告任何垃圾信息或广告. Web项目常常需要将数据尽可能快地推送给客户,必要时无需等待客户端请求.对于与用户之间进行实时通信的网站,例如在线交流或文档协作工具,或者在长期运行的计算/执行任务的服务器上更新系统状态,等等这些时候,采用双向沟通机制是理想的. 以前,这类问题一般使用下面的解决方案: 使用 Flash 中的 Socket 连接(http://help.adobe.c

Windows 8 Store Apps学习(63) 通信: WebSocket

介绍 重新想象 Windows 8 Store Apps 之 通信 Socket - 与 WebSocket 服务端做 Text 通信 Socket - 与 WebSocket 服务端做 Stream(Binary) 通信 示例 WebSocket 的服务端 WebServer/WebSocketServer.ashx.cs /* * WebSocket 协议的服务端 * * 需要在 iis 启用 WebSocket 协议:控制面板 -> 程序和功能 -> 启用或关闭 Windows 功能 -

带你认识HTML5中的WebSocket

 认识 HTML5 的 WebSocket 在 HTML5 规范中,我最喜欢的Web技术就是正迅速变得流行的 WebSocket API.WebSocket 提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术.这个新的API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器.让我们看一看 HTML5 的 WebSocket API:它可用于客户端.服务器端.而且有一个优秀的第三方API,名为Socket.IO. 一.HTML5 中的 WebSocket API 是个什么东

使用swoole扩展php websocket示例

 WebSocket规范的目标是在浏览器中实现和服务器端双向通信.双向通信可以拓展浏览器上的应用类型,如果你想要用PHP来写websocket应用,那swoole_framework一定是最好的选择,需要的朋友可以参考下  代码如下: <?php define('DEBUG', 'on'); define("WEBPATH", str_replace("","/", __DIR__)); require __DIR__ . '/../lib

使用Poco C++库创建websocket安全访问(wss)客户端

  Poco websocket库特点: 1,使用http/https ClientSession创建websocket client 2,是同步的,这对C++桌面编程来说应该是够用的. 3,依赖openssl.  代码如下   #include "stdafx.h" #include <iostream> #include <assert.h>   #include "Poco/Net/WebSocket.h" #include "

php使用websocket示例

 这篇文章主要介绍了php使用websocket示例,需要的朋友可以参考下 下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket 套接字做了封装处理,开发者使用的时候只需要考虑数据的交互而不用处理连接的建立.而 php 没有,从 socket 的连接.建立.绑定.监听等,这些都需要我们自己去操作,所以有必要拿出来再说一说.     ① 和 ② 实际上