Heim  >  Artikel  >  Backend-Entwicklung  >  Go-Websocket-Implementierung (mit Code)

Go-Websocket-Implementierung (mit Code)

尚
nach vorne
2019-11-25 15:02:533593Durchsuche

Go-Websocket-Implementierung (mit Code)

Websocket ist in Handshake- und Datenübertragungsphasen unterteilt, nämlich HTTP-Handshake + Duplex-TCP-Verbindung

Handshake-Phase

Die Handshake-Phase ist normales HTTP

Der Client sendet eine Nachricht:

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

Der Server gibt eine Nachricht zurück:

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

Hier ist Sec-WebSocket- Die Berechnungsmethode von Accept lautet:

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

Wenn die Sec-WebSocket-Accept-Berechnung falsch ist, zeigt der Browser Folgendes an:

Sec-WebSocket-Accept-Diskrepanz

Wenn die Rückkehr ist erfolgreich, Websocket Das onopen-Ereignis wird zurückgerufen

Datenübertragung

Das für die Datenübertragung von Websocket verwendete Protokoll ist:

Go-Websocket-Implementierung (mit Code)

Spezifische Beschreibung der Parameter:

FIN: 1 Bit, wird verwendet, um anzuzeigen, dass dies das letzte Nachrichtenfragment einer Nachricht ist Natürlich kann das erste Nachrichtenfragment auch das letzte A-Nachrichtenfragment sein; 0 sein, andernfalls muss die WebSocket-Verbindung getrennt werden ;

Opcode: 4-Bit-Opcode, der Nutzdaten definiert. Wenn ein unbekannter Opcode empfangen wird, muss die Verbindung getrennt werden:

* %x0 stellt fortlaufende Nachrichtenfragmente dar

* %x1 stellt Textnachrichtenfragmente dar

* %x2 stellt binäre Nachrichtenfragmente dar

* %x3-7 sind reserviert für zukünftige nicht kontrollierte Nachrichtenfragmente Der Opcode von

* %x8 bedeutet, dass die Verbindung geschlossen ist

* %x9 bedeutet der Ping der Heartbeat-Prüfung

* %xA bedeutet der Pong der Heartbeat-Prüfung

* %xB-F ist ein reservierter Opcode für zukünftige Steuernachrichtenfragmente

Maske:

1 Bit, das definiert, ob die übertragenen Daten vorhanden sind maskiert. Wenn der Maskenschlüssel auf 1 gesetzt ist, muss er im Maskierungsschlüsselbereich platziert werden. Der Wert dieses Bits ist 1 für alle vom Client an den Server gesendeten Nachrichten.

Nutzlastlänge:

Die Länge der übertragenen Daten, ausgedrückt in Bytes: 7 Bits, 7+16 Bits oder 7+64 Bits.

Maskierungsschlüssel:

0 oder 4 Bytes. Die vom Client an den Server gesendeten Daten werden durch einen eingebetteten 32-Bit-Wert maskiert ist auf 1 gesetzt.

Nutzdaten:

(x+y) Bits, die Nutzdaten sind die Summe der Länge der erweiterten Daten und Anwendungsdaten.

Erweiterungsdaten:

x Bits Wenn zwischen Client und Server keine besondere Vereinbarung besteht, ist die Länge der Erweiterungsdaten immer 0. Jede Erweiterung muss die Länge der Erweiterung angeben Oder wie die Länge berechnet wird und wie man den richtigen Händedruck beim Händeschütteln ermittelt. Wenn Erweiterungsdaten vorhanden sind, werden die Erweiterungsdaten in die Länge der Nutzdaten einbezogen.

Anwendungsdaten:

y Bits, alle Anwendungsdaten, nach den erweiterten Daten platziert, die Länge der Anwendungsdaten = die Länge der Ladedaten – die Länge der erweiterten Daten.

BeispielSpezifisches Implementierungsbeispiel mit 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
}

Für weitere Go-Sprachkenntnisse beachten Sie bitte die Spalte

Go-Sprach-Tutorial

.

Das obige ist der detaillierte Inhalt vonGo-Websocket-Implementierung (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen