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

    Image

    参数的具体说明在这:

    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:

    <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/

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

    实时了解作者更多技术文章,技术心得,请关注微信公众号“轩脉刃的刀光剑影”

    本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名叶剑峰(包含链接http://www.cnblogs.com/yjf512/),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

  • 相关阅读:
    IT教育课程考评系统开发-07
    2020091201-1
    ip
    输入框枚举
    语言枚举
    《岁月神偷》弹唱和弦吉他谱_六线谱
    string 转化成 string数组
    获取类的字段值
    获取类的字段
    最全的省份递归
  • 原文地址:https://www.cnblogs.com/yjf512/p/2915171.html
Copyright © 2020-2023  润新知