• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    go的websocket实现原理与用法详解

    本文实例讲述了go的websocket实现原理与用法。分享给大家供大家参考,具体如下:

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

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

    握手阶段

    握手阶段就是普通的HTTP

    客户端发送消息:

    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
    
    

    服务端返回消息:

    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位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。

    实例

    具体使用go的实现例子:

    客户端:

    html:

    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:

    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();
    })
    
    

    服务端:

    复制代码 代码如下:
    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/

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

    希望本文所述对大家Go语言程序设计有所帮助。

    您可能感兴趣的文章:
    • 利用Go语言搭建WebSocket服务端方法示例
    • golang基于websocket实现的简易聊天室程序
    • 让谷歌浏览器Google Chrome支持eWebEditor的方法
    • 如何解决ajax在google chrome浏览器上失效
    • Go语言实现基于websocket浏览器通知功能
    上一篇:golang两种调用rpc的方法
    下一篇:go语言的sql包原理与用法分析
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    go的websocket实现原理与用法详解 的,websocket,实现,原理,与,