zoukankan      html  css  js  c++  java
  • 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的数据传输使用的协议是:

    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:

    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 "
            response = response + "Sec-WebSocket-Accept: " + string(accept) + " "
            response = response + "Connection: Upgrade "
            response = response + "Upgrade: websocket "
             
                 
            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, " ")
     
        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/

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

  • 相关阅读:
    各个版本中Notification对象创建的方法
    数据结构一:线性表
    安装eclipse中文汉化包后无法打开eclipse【转】
    在MFC里面使用自定义的OpenGL类进行绘图(基于VS2010)
    2016-2-25 我的博客开通了
    从C#到Swift原来这么简单,So Easy!
    CocoaPods安装及使用(包含重装步骤)
    Xcode键盘快捷键
    参考资料收集
    重温算法和数据结构:二分查找
  • 原文地址:https://www.cnblogs.com/php-rearch/p/5966085.html
Copyright © 2011-2022 走看看