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-12-10 01:17:14

go的websocket实现的相关文章

web新特性 之 WebSocket

详情参见:你真的了解WebSocket吗?     WebSocket系列教程   HTML5新特性之WebSocket WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与服务器全双工(full-duplex)通信.其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信. 服务端与客户端的连接不断开,实现全双工的操作.及服务端或是客户端都会给对方发送消息. WebSocke

websocket之拨云见雾

websocket是基于http相应的特性弥补其不足(就是个socket,不再是一次请求一次相应) 但缺点就是只有在版本较高的浏览器才支持websocket. 浏览器: <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); ... </script> 服务端: import socket sock = socket.soc

websocket

什么是websocket? 不错的介绍:https://www.zhihu.com/question/20215561 websocket是一种持久化的协议,实现了浏览器和服务端的全双工通信: 握手阶段与HTTP协议相同,返回状态码101(switching protocols): 握手阶段完成之后,按照websocket协议进行通信 应用场景: 浏览器与服务端需要实时通信的场景,以前的解决方式:ajax轮询.HTTP长连接.Flash等. 实现: 浏览器端 const ws = new Web

websocket链接数限制

无法解决 当我需要每跳转新打开一个页面链接一次websocket的时候,第六个开始loading,关掉前面的,会加载出来,查了查.... 由于websocket属于属于持久链接 不同的浏览器针对同一服务器的持久链接的数量限制如下: chrome  6 firefox  6 Safari 5 IE11以下 不支持 这种H5的新新技术不得不说有各种安全和兼容性问题,慎重选用

java SSM框架 多数据源 代码生成器 websocket即时通讯 shiro redis 后台框架源码

获取[下载地址]   QQ: 313596790官网 http://www.fhadmin.org/A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块C 集成阿里巴巴数据库连接池druid  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都

异步通信----WebSocket

什么是WebSocket? WebSocket API是下一代客户端-服务器的异步通信方法.该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序.WebSocket目前由W3C进行标准化.WebSocket已经受到Firefox 4.Chrome 4.Opera 10.70以及Safari 5等浏览器的支持. WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息.WebSocket并不限于以Ajax(或XHR)方式通

QDjango,tufao,C++ websocket client/server

QDjango, a Qt-based C++ web frameworkhttps://github.com/jlaine/qdjango/ An asynchronous web framework for C++ built on top of Qt http://vinipsmaker.github.io/tufao/https://github.com/vinipsmaker/tufao C++ websocket client/server library http://www.za

PHP基于websocket实时通信的实现—GoEasy

PHP websocket实时消息推送 实现步骤如下: 1. 获取 GoEasy appkey. 在 goeasy 官网上注册一个账号,并新建一个 app. APP 创建好后系统会为该 app 自动生成两个 key, 一个既可以用来接收又可以用来推送 (supper key),另一个只可以用来接收(subscriber key). 2. 客户端订阅一个 channel. a. 在客户端引入 goeasy.js , js 地址: http://cdn.goeasy.io/goeasy.js 需要注

websocket实战(1) 入门

1.WebSocket介绍 1.1 概念 WebSocket是HTML5中一系列新的API,或者说新规范,新技术.支持页面上使用Web Socket协议与远程主机进行全双工的通信.它引入了WebSocket接口并且定义了一个全双工的通信通道,通过一个单一的套接字在Web上进行操作. 1.2 websocket vs HTTP 首先,web技术发展经历了以下阶段. 静态页面(html) 动态页面(cgi,j2ee,php...) Ajax技术 comet技术(轮询) 1.2.1 实现方案对比 举个

认识HTML5的WebSocket

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