ホームページ > バックエンド開発 > Golang > Go WebSocket の実装 (コード付き)

Go WebSocket の実装 (コード付き)

リリース: 2019-11-25 15:02:53
転載
3725 人が閲覧しました

Go WebSocket の実装 (コード付き)

Websocket は、ハンドシェイク ステージとデータ送信ステージ、つまり HTTP ハンドシェイク二重による TCP 接続

ハンドシェイク ステージ ## に分かれています。

#ハンドシェイク フェーズは通常の 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

If the return成功すると、Websocket は onopen イベントがコールバックされます。

データ送信

Websocket のデータ送信に使用されるプロトコルは次のとおりです:

Go WebSocket の実装 (コード付き)

パラメータの具体的な説明:

FIN: 1 ビット、これがメッセージの最後のメッセージ フラグメントであることを示すために使用されます。もちろん、最初のメッセージ フラグメントが最後の A メッセージ フラグメントである場合もあります;

RSV1、RSV2、RSV3: 各ビットは 1 ビットです。両者の間で合意されたカスタム プロトコルがない場合、これらのビットの値は0 である場合は、WebSocket 接続を切断する必要があります;

オペコード: 4 桁のオペコード、ペイロード データを定義します。不明なオペコードを受信した場合、接続を切断する必要があります。定義されたオペコードは次のとおりです:

* %x0 は連続メッセージ フラグメントを表します

* %x1 はテキスト メッセージ フラグメントを表します

* %x2 はバイナリ メッセージ フラグメントを表します

* %x3 ~ 7 は将来の非制御メッセージ フラグメント用に予約されています オペレーション コード

* %x8 は接続が閉じられたことを意味します

* %x9 はハートビート チェックの ping

を意味します * %xA はポン ##​​# を意味しますハートビート チェックの

* % xB-F は将来の制御メッセージ フラグメント用に予約されたオペコードです

Mask:

1 ビットで、送信データがマスクされるかどうかを定義します。1 に設定されている場合、マスク キーは次でなければなりません。マスキング キー領域では、このビットの値は、クライアントからサーバーに送信されるすべてのメッセージに対して 1 です。

Payload length:

バイト単位で表される送信データ: 7 ビット、7 16 ビット、または 7 64 ビット。

Masking-key:

0 または 4 バイト、クライアントからサーバーに送信されるデータは、埋め込まれた 32 ビット値によってマスクされます。マスク キーは、マスク ビットが存在する場合にのみ存在します。は 1 に設定されます。

ペイロード データ:

(x y) ビット、ペイロード データは拡張データとアプリケーション データの長さの合計です。

拡張データ:#xx ビット。クライアントとサーバーの間に特別な取り決めがない場合、拡張データの長さは常に 0 です。拡張では、拡張の長さを指定する必要があります。データや、長さの計算方法、握手の際の正しい握手の決め方など。拡張データが存在する場合、拡張データはペイロードデータの長さに含まれます。

アプリケーション データ:

y ビット、拡張データの後に配置される任意のアプリケーション データ アプリケーション データの長さ = ロード データの長さ - 拡張データの長さ。

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();
})
ログイン後にコピー

サーバー:

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
}
ログイン後にコピー

Go 言語の知識については、

go 言語チュートリアル

列に注目してください。

以上がGo WebSocket の実装 (コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
go
ソース:cnblogs.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート