xuanwenchao 2020-05-13
ä»å¤©ä»ç»å¦ä½ç¨ Go
è¯è¨å建 WebSocket
æå¡ï¼æç« çå两é¨åç®è¦ä»ç»äº WebSocket
å议以åç¨ Go
æ ååºå¦ä½å建 WebSocket
æå¡ã第ä¸é¨åå®è·µç¯èæ们使ç¨äº gorilla/websocket
åºå¸®å©æ们快éæ建 WebSocket
æå¡ï¼å®å¸®å°è£äºä½¿ç¨ Go
æ ååºå®ç° WebSocket
æå¡ç¸å³çåºç¡é»è¾ï¼è®©æ们è½ä»ç¹ççåºå±ä»£ç ä¸è§£è±åºæ¥ï¼æ ¹æ®ä¸å¡éæ±å¿«éæ建 WebSocket
æå¡ã
Go Web ç¼ç¨ç³»åçæ¯ç¯æç« çæºä»£ç é½æäºå¯¹åºçæ¬ç软件åï¼ä¾å¤§å®¶åèãå¬ä¼å·ä¸åå¤ gohttp10
è·åæ¬ææºä»£ç
WebSocketä»ç»
WebSocket
éä¿¡åè®®éè¿å个 TCP
è¿æ¥æä¾å¨åå·¥éä¿¡ééãä¸ HTTP
ç¸æ¯ï¼ WebSocket
ä¸éè¦ä½ 为äºè·å¾ååºèåé请æ±ãå®å许ååæ°æ®æµï¼å æ¤æ¨åªéçå¾æå¡å¨åéçæ¶æ¯å³å¯ãå½ WebSocket
å¯ç¨æ¶ï¼å®å°åæ¨åéä¸æ¡æ¶æ¯ã 对äºéè¦è¿ç»æ°æ®äº¤æ¢çæå¡ï¼ä¾å¦å³æ¶é讯ç¨åºï¼å¨çº¿æ¸¸æåå®æ¶äº¤æç³»ç»ï¼ï¼ WebSocket
æ¯ä¸ä¸ªå¾å¥½ç解å³æ¹æ¡ã WebSocket
è¿æ¥ç±æµè§å¨è¯·æ±ï¼å¹¶ç±æå¡å¨ååºï¼ç¶å建ç«è¿æ¥ï¼æ¤è¿ç¨é常称为æ¡æã WebSocket
ä¸çç¹æ®æ 头ä»éè¦æµè§å¨ä¸æå¡å¨ä¹é´çä¸æ¬¡æ¡æå³å¯å»ºç«è¿æ¥ï¼è¯¥è¿æ¥å°å¨å¶æ´ä¸ªçå½å¨æåä¿ææ´»å¨ç¶æã WebSocket
解å³äºè®¸å¤å®æ¶ Web
å¼åçé¾é¢ï¼å¹¶ä¸ä¸ä¼ ç»ç HTTP
ç¸æ¯ï¼å·æ许å¤ä¼ç¹ï¼
WebSocketåè®®å®ç°èµ·æ¥ç¸å¯¹ç®åãå®ä½¿ç¨ HTTP
åè®®è¿è¡åå§æ¡æãæ¡ææååå³å»ºç«è¿æ¥ï¼ WebSocket
å®è´¨ä¸ä½¿ç¨åå§ TCP
读å/åå¥æ°æ®ã
客æ·ç«¯è¯·æ±å¦ä¸æ示ï¼
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
è¿æ¯æå¡å¨ååºï¼
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
å¦ä½å¨Goä¸å建WebSocketåºç¨
è¦åºäºGo è¯è¨åç½®ç net/http
åºç¼å WebSocket
æå¡å¨ï¼ä½ éè¦ï¼
åèµ·æ¡æ
é¦åï¼è®©æ们å建ä¸ä¸ªå¸¦æ WebSocket
端ç¹ç HTTP
å¤çç¨åºï¼
// HTTP server with WebSocket endpoint func Server() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ws, err := NewHandler(w, r) if err != nil { // handle error } if err = ws.Handshake(); err != nil { // handle error } â¦
ç¶ååå§å WebSocket
ç»æã
åå§æ¡æ请æ±å§ç»æ¥èªå®¢æ·ç«¯ãæå¡å¨ç¡®å®äº WebSocket
请æ±åï¼éè¦ä½¿ç¨æ¡æååºè¿è¡åå¤ã
请记ä½ï¼ä½ æ æ³ä½¿ç¨ http.ResponseWriter
ç¼åååºï¼å 为ä¸æ¦å¼å§åéååºï¼å®å°å³éå¶åºç¡ç TCP
è¿æ¥ï¼è¿æ¯ HTTP
åè®®çè¿è¡æºå¶å³å®çï¼åéååºåå³å³éè¿æ¥ï¼ã
å æ¤ï¼æ¨éè¦ä½¿ç¨ HTTP
å«æ( hijack
)ãéè¿å«æï¼å¯ä»¥æ¥ç®¡åºç¡ç TCP
è¿æ¥å¤çç¨åºå bufio.Writer
ãè¿ä½¿å¯ä»¥å¨ä¸å³é TCP
è¿æ¥çæåµä¸è¯»åååå¥æ°æ®ã
// NewHandler initializes a new handler func NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) { hj, ok := w.(http.Hijacker) if !ok { // handle error } ..... }
è¦å®ææ¡æï¼æå¡å¨å¿é¡»ä½¿ç¨éå½ç头è¿è¡ååºã
// Handshake creates a handshake header func (ws *WS) Handshake() error { hash := func(key string) string { h := sha1.New() h.Write([]byte(key)) h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) return base64.StdEncoding.EncodeToString(h.Sum(nil)) }(ws.header.Get("Sec-WebSocket-Key")) ..... }
客æ·ç«¯åèµ· WebSocket
è¿æ¥è¯·æ±æ¶ç¨ç Sec-WebSocket-key
æ¯éæºçæçï¼å¹¶ä¸æ¯Base64ç¼ç çãæ¥å请æ±åï¼æå¡å¨éè¦å°æ¤å¯é¥éå å°åºå®å符串ãå设ç§é¥æ¯ x3JJHMbDL1EzLkh9GBhXDw==
ãå¨è¿ä¸ªä¾åä¸ï¼å¯ä»¥ä½¿ç¨ SHA-1
计ç®äºè¿å¶å¼ï¼å¹¶ä½¿ç¨ Base64
对å¶è¿è¡ç¼ç ãå¾å° HSmrc0sMlYUkAGmm5OPpG2HaGWk=
ãç¶å使ç¨å®ä½ä¸º Sec-WebSocket-Accept
ååºå¤´çå¼ã
ä¼ è¾æ°æ®å¸§
æ¡ææåå®æåï¼æ¨çåºç¨ç¨åºå¯ä»¥ä»å®¢æ·ç«¯è¯»åæ°æ®æå客æ·ç«¯åå¥æ°æ®ãWebSocketè§è å®ä¹äºçä¸ä¸ªå®¢æ·æºåæå¡å¨ä¹é´ä½¿ç¨çç¹å®å¸§æ ¼å¼ãè¿æ¯æ¡æ¶çä½æ¨¡å¼ï¼
å¾:ä¼ è¾æ°æ®å¸§çä½æ¨¡å¼
使ç¨ä»¥ä¸ä»£ç 对客æ·ç«¯ææè´è½½è¿è¡è§£ç ï¼
// Recv receives data and returns a Frame func (ws *WS) Recv() (frame Frame, _ error) { frame = Frame{} head, err := ws.read(2) if err != nil { // handle error }
åè¿æ¥ï¼è¿äºä»£ç è¡å许对æ°æ®è¿è¡ç¼ç ï¼
// Send sends a Frame func (ws *WS) Send(fr Frame) error { // make a slice of bytes of length 2 data := make([]byte, 2) // Save fragmentation & opcode information in the first byte data[0] = 0x80 | fr.Opcode if fr.IsFragment { data[0] &= 0x7F } .....
å³éæ¡æ
å½åæ¹ä¹ä¸åéç¶æ为å³éçå³é帧ä½ä¸ºææè´è½½æ¶ï¼æ¡æå°å³éãå¯éçï¼åéå³é帧çä¸æ¹å¯ä»¥å¨ææè½½è·ä¸åéå³éåå ãå¦æå³éæ¯ç±å®¢æ·ç«¯åèµ·çï¼åæå¡å¨åºåéç¸åºçå³é帧ä½ä¸ºååºã
// Close sends a close frame and closes the TCP connection func (ws *Ws) Close() error { f := Frame{} f.Opcode = 8 f.Length = 2 f.Payload = make([]byte, 2) binary.BigEndian.PutUint16(f.Payload, ws.status) if err := ws.Send(f); err != nil { return err } return ws.conn.Close() }
使ç¨ç¬¬ä¸æ¹åºå¿«éæ建WebSocketæå¡
éè¿ä¸é¢çç« èå¯ä»¥çå°ç¨ Go
èªå¸¦ç net/http
åºå®ç° WebSocket
æå¡è¿æ¯å¤ªå¤æäºã好å¨æå¾å¤å¯¹ WebSocket
æ¯æè¯å¥½ç第ä¸æ¹åºï¼è½åå°æ们å¾å¤åºå±çç¼ç å·¥ä½ãè¿éæä»¬ä½¿ç¨ gorilla web toolkit
家æçå¦å¤ä¸ä¸ªåº gorilla/websocket
æ¥å®ç°æ们ç WebSocket
æå¡ï¼æ建ä¸ä¸ªç®åç Echo
æå¡ï¼ Echo
æææ¯åé³ï¼å°±æ¯å®¢æ·ç«¯åä»ä¹ï¼æå¡ç«¯åææ¶æ¯ååç»å®¢æ·ç«¯ï¼ã
æä»¬å¨ http_demo
项ç®ç handler
ç®å½ä¸æ°å»ºä¸ä¸ª ws
åç®å½ç¨æ¥åæ¾ WebSocket
æå¡ç¸å³çè·¯ç±å¯¹åºç请æ±å¤çç¨åºã
å¢å 两个路ç±ï¼
/ws/echo
Echo
åºç¨çWebSocket æå¡çè·¯ç±/ws/echo_display
Echo
åºç¨ç客æ·ç«¯é¡µé¢çè·¯ç±ã å建WebSocketæå¡ç«¯// handler/ws/echo.go package ws import ( "fmt" "github.com/gorilla/websocket" "net/http" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func EchoMessage(w http.ResponseWriter, r *http.Request) { conn, _ := upgrader.Upgrade(w, r, nil) // å®éåºç¨æ¶è®°å¾åé误å¤ç for { // 读å客æ·ç«¯çæ¶æ¯ msgType, msg, err := conn.ReadMessage() if err != nil { return } // ææ¶æ¯æå°å°æ åè¾åº fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg)) // ææ¶æ¯åå客æ·ç«¯ï¼å®æåé³ if err = conn.WriteMessage(msgType, msg); err != nil { return } } }
conn
åéçç±»åæ¯ *websocket.Conn
, websocket.Conn
ç±»åç¨æ¥è¡¨ç¤º WebSocket
è¿æ¥ãæå¡å¨åºç¨ç¨åºä» HTTP
请æ±å¤çç¨åºè°ç¨ Upgrader.Upgrade
æ¹æ³ä»¥è·å *websocket.Conn
WriteMessage
å ReadMessage
æ¹æ³åéåæ¥æ¶æ¶æ¯ãä¸é¢ç msg
æ¥æ¶å°åå¨ä¸é¢ååä¼ ç»äºå®¢æ·ç«¯ã msg
çç±»åæ¯ []byte
ãå建WebSocket客æ·ç«¯
å端页é¢è·¯ç±å¯¹åºç请æ±å¤çç¨åºå¦ä¸ï¼ç´æ¥è¿å views/websockets.html
ç»å°æµè§å¨æ¸²æ页é¢å³å¯ã
// handler/ws/echo_display.go package ws import "net/http" func DisplayEcho(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "views/websockets.html") }
websocket.html
éæ们éè¦ç¨ JavaScript
è¿æ¥ WebScoket
æå¡è¿è¡æ¶åæ¶æ¯ï¼ç¯å¹åå æå°±åªè´´ JS
代ç äº
<form> <input id="input" type="text" /> <button onclick="send()">Send</button> <pre id="output"></pre> </form> ... <script> var input = document.getElementById("input"); var output = document.getElementById("output"); var socket = new WebSocket("ws://localhost:8000/ws/echo"); socket.onopen = function () { output.innerHTML += "Status: Connected\n"; }; socket.onmessage = function (e) { output.innerHTML += "Server: " + e.data + "\n"; }; function send() { socket.send(input.value); input.value = ""; } </script> ...
注åè·¯ç±
æå¡ç«¯å客æ·ç«¯çç¨åºé½åå¤å¥½åï¼æ们æç§ä¹å约å®å¥½çè·¯å¾ä¸ºä»ä»¬æ³¨åè·¯ç±å对åºç请æ±å¤çç¨åºï¼
// router/router.go func RegisterRoutes(r *mux.Router) { ... wsRouter := r.PathPrefix("/ws").Subrouter() wsRouter.HandleFunc("/echo", ws.EchoMessage) wsRouter.HandleFunc("/echo_display", ws.DisplayEcho) }
æµè¯éªè¯
éå¯æå¡åè®¿é® http://localhost:8000/ws/echo_display
ï¼å¨è¾å¥æ¡ä¸è¾å¥ä»»ä½æ¶æ¯é½è½å次åæ¾å°æµè§å¨ä¸ã
æå¡ç«¯åæ¯ææ¶å°çæ¶æ¯æå°å°ç»ç«¯ä¸ç¶åæè°ç¨ WriteMessage
ææ¶æ¯ååä¼ ç»å®¢æ·ç«¯ï¼å¯ä»¥å¨ç»ç«¯ä¸æ¥çå°è®°å½ã
æ»ç»
WebSocket
å¨ç°å¨æ´æ°é¢ç¹çåºç¨ä¸ä½¿ç¨é常广æ³ï¼è¿è¡ WebSocket
ç¼ç¨ä¹æ¯æ们éè¦ææ¡çä¸é¡¹å¿å¤æè½ãæç« çå®è·µç»ä¹ ç¨å¾®ç®åäºä¸äºï¼ä¹æ²¡æåé误åå®å¨æ§æ£æ¥ã主è¦æ¯ä¸ºäºè®²æ¸æ¥å¤§æ¦çæµç¨ãå³äº gorilla/websocket
æ´å¤çç»èå¨ä½¿ç¨æ¶è¿éè¦æ¥çå®æ¹ææ¡£æè¡ã
åèé¾æ¥ï¼
https://yalantis.com/blog/how-to-build-websockets-in-go/
https://www.gorillatoolkit.org/pkg/websocket
å°æ¤è¿ç¯å³äºä½¿ç¨Goè¯è¨å建WebSocketæå¡çå®ç°ç¤ºä¾çæç« å°±ä»ç»å°è¿äº,æ´å¤ç¸å³Goè¯è¨å建WebSocket å容请æç´¢èæ¬ä¹å®¶ä»¥åçæç« æ继ç»æµè§ä¸é¢çç¸å³æç« å¸æ大家以åå¤å¤æ¯æèæ¬ä¹å®¶ï¼