Maison  >  Article  >  développement back-end  >  Aller à l'implémentation de Websocket (avec code)

Aller à l'implémentation de Websocket (avec code)

尚
avant
2019-11-25 15:02:533657parcourir

Aller à l'implémentation de Websocket (avec code)

Websocket est divisé en étapes de prise de contact et de transmission de données, c'est-à-dire prise de contact HTTP + connexion TCP duplex

Étape de prise de contact

La phase de prise de contact est HTTP ordinaire

Le client envoie un 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

Le serveur renvoie un message :

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

Voici Sec-WebSocket- La méthode de calcul d'Accept est :

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

Si le calcul Sec-WebSocket-Accept est incorrect, le navigateur demandera :

Sec-WebSocket-Accept dismatch

Si le le retour est réussi, Websocket L'événement onopen sera rappelé

Transmission de données

Le protocole utilisé pour la transmission de données de websocket est :

Aller à limplémentation de Websocket (avec code)

Description spécifique des paramètres :

FIN : 1 bit, utilisé pour indiquer qu'il s'agit du dernier fragment de message d'un message. bien sûr, le premier fragment de message peut aussi être le dernier fragment de message A

RSV1, RSV2, RSV3 : chacun vaut 1 bit. S'il n'y a pas de protocole personnalisé convenu entre les deux parties, la valeur de ces bits doit être définie. être 0, sinon la connexion WebSocket doit être déconnectée ;

Opcode : opcode 4 bits, définissant les données utiles. Si un opcode inconnu est reçu, la connexion doit être déconnectée :

* %x0 Représente des fragments de messages continus

* %x1 représente des fragments de messages texte

* %x2 représente des fragments de messages binaires

* %x3-7 sont réservés pour les futurs fragments de message non contrôlés L'opcode de

* %x8 signifie que la connexion est fermée

* %x9 signifie le ping de la vérification du rythme cardiaque

* %xA signifie le pong de la vérification du rythme cardiaque

* %xB-F est un opcode réservé pour les futurs fragments de messages de contrôle

Masque : 1 bit, qui définit si les données transmises sont masqué. Si défini sur 1, la clé de masque doit être placée dans la zone de clé de masquage, la valeur de ce bit est 1 pour tous les messages envoyés par le client au serveur

Longueur de la charge utile : La longueur des données transmises, exprimée en octets : 7 bits, 7+16 bits, ou 7+64 bits.

Masking-key : 0 ou 4 octets Les données envoyées du client au serveur sont masquées par une valeur de 32 bits intégrée. La clé de masque n'existe que lorsque le bit de masque est présent. est mis à 1.

Données utiles : (x+y) bits, les données utiles sont la somme de la longueur des données étendues et des données d'application.

Données d'extension :x bits S'il n'y a pas d'accord particulier entre le client et le serveur, la longueur des données d'extension est toujours 0. Toute extension doit spécifier la longueur de l'extension. données Ou comment la longueur est calculée et comment déterminer la poignée de main correcte lors d'une poignée de main. Si des données d'extension sont présentes, les données d'extension sont incluses dans la longueur des données de charge utile.

Données d'application : y bits, toutes données d'application, placées après les données étendues, la longueur des données d'application = la longueur des données de chargement - la longueur des données étendues.

Exemple

Exemple d'implémentation spécifique utilisant 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();
})

Serveur :

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
}

Pour plus de connaissances sur la langue go, veuillez faire attention à la colonne

tutoriel sur la langue go.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer