Home >Backend Development >Golang >Go websocket implementation (with code)

Go websocket implementation (with code)

尚
forward
2019-11-25 15:02:533726browse

Go websocket implementation (with code)

Websocket is divided into handshake and data transmission stages, that is, TCP connection with HTTP handshake duplex

Handshake stage

The handshake phase is ordinary HTTP

The client sends a message:

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

The server returns a message:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Here's Sec-WebSocket-Accept The calculation method is:

base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

If the Sec-WebSocket-Accept calculation is incorrect, the browser will prompt:

Sec-WebSocket-Accept dismatch

If the return is successful, Websocket will The onopen event will be called back

Data transmission

The protocol used for data transmission of websocket is:

Go websocket implementation (with code)

Specific description of parameters:

FIN: 1 bit, used to indicate that this is the last message fragment of a message. Of course, the first message fragment may also be the last A message fragment;

RSV1, RSV2, RSV3: each is 1 bit. If there is no custom protocol agreed between the two parties, the value of these bits must be 0, otherwise the WebSocket connection must be disconnected;

Opcode: 4-digit opcode, defines the payload data. If an unknown opcode is received, the connection must be disconnected. The following is the defined opcode:

* %x0 means Continuous message fragments

* %x1 represents text message fragments

* %x2 represents binary message fragments

* %x3-7 are reserved for future non-control message fragments Operation code

* %x8 means connection closed

* %x9 means ping

of heartbeat check * %xA means pong

of heartbeat check * % xB-F is the reserved opcode for future control message fragments

Mask:1 bit, which defines whether the transmitted data is masked. If set to 1, the mask key must be In the masking-key area, the value of this bit is 1 for all messages sent by the client to the server;

Payload length: The length of the transmitted data, expressed in bytes: 7 bits, 7 16 bits, or 7 64 bits.

Masking-key:0 or 4 bytes, the data sent from the client to the server is masked by an embedded 32-bit value; the mask key is only Exists when the mask bit is set to 1.

Payload data: (x y) bits, the payload data is the sum of the length of extended data and application data.

Extension data:x bit, if there is no special agreement between the client and the server, the length of the extension data is always 0. Any extension must specify the length of the extension data. Or how the length is calculated, and how to determine the correct handshake when shaking hands. If extension data is present, the extension data is included in the length of the payload data.

Application data: y bits, any application data, placed after the extended data. The length of the application data = the length of the load data - the length of the extended data.

Examples

Specific implementation examples using go:

Client:

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

Server:

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
}

For more go language knowledge, please pay attention to the go language tutorial column.

The above is the detailed content of Go websocket implementation (with code). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete