Heim > Artikel > Backend-Entwicklung > 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:
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-TutorialDas 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!