使用Go语言创建WebSocket服务的实现示例

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 ç¸æ¯ï¼å·æ许å¤ä¼ç¹ï¼

  1. è½»é级æ¥å¤´åå°äºæ°æ®ä¼ è¾å¼éã
  2. å个Web客æ·ç«¯ä»éè¦ä¸ä¸ªTCPè¿æ¥ã
  3. WebSocketæå¡å¨å¯ä»¥å°æ°æ®æ¨éå°Web客æ·ç«¯ã

WebSocketåè®®å®ç°èµ·æ¥ç¸å¯¹ç®åãå®ä½¿ç¨ HTTP åè®®è¿è¡åå§æ¡æãæ¡ææååå³å»ºç«è¿æ¥ï¼ WebSocket å®è´¨ä¸ä½¿ç¨åå§ TCP 读å/åå¥æ°æ®ã

使用Go语言创建WebSocket服务的实现示例

客æ·ç«¯è¯·æ±å¦ä¸æ示ï¼

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è§è å®ä¹äºçä¸ä¸ªå®¢æ·æºåæå¡å¨ä¹é´ä½¿ç¨çç¹å®å¸§æ ¼å¼ãè¿æ¯æ¡æ¶çä½æ¨¡å¼ï¼

使用Go语言创建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 ï¼å¨è¾å¥æ¡ä¸­è¾å¥ä»»ä½æ¶æ¯é½è½å次åæ¾å°æµè§å¨ä¸­ã

使用Go语言创建WebSocket服务的实现示例

æå¡ç«¯åæ¯ææ¶å°çæ¶æ¯æå°å°ç»ç«¯ä¸­ç¶åæè°ç¨ WriteMessage ææ¶æ¯ååä¼ ç»å®¢æ·ç«¯ï¼å¯ä»¥å¨ç»ç«¯ä¸­æ¥çå°è®°å½ã

使用Go语言创建WebSocket服务的实现示例 æ»ç»

WebSocket å¨ç°å¨æ´æ°é¢ç¹çåºç¨ä¸­ä½¿ç¨é常广æ³ï¼è¿è¡ WebSocket ç¼ç¨ä¹æ¯æ们éè¦ææ¡çä¸é¡¹å¿å¤æè½ãæç« çå®è·µç»ä¹ ç¨å¾®ç®åäºä¸äºï¼ä¹æ²¡æåé误åå®å¨æ§æ£æ¥ã主è¦æ¯ä¸ºäºè®²æ¸æ¥å¤§æ¦çæµç¨ãå³äº gorilla/websocket æ´å¤çç»èå¨ä½¿ç¨æ¶è¿éè¦æ¥çå®æ¹ææ¡£æè¡ã

åèé¾æ¥ï¼

https://yalantis.com/blog/how-to-build-websockets-in-go/

https://www.gorillatoolkit.org/pkg/websocket

å°æ­¤è¿ç¯å³äºä½¿ç¨Go语è¨å建WebSocketæå¡çå®ç°ç¤ºä¾çæç« å°±ä»ç»å°è¿äº,æ´å¤ç¸å³Go语è¨å建WebSocket å容请æç´¢èæ¬ä¹å®¶ä»¥åçæç« æ继续æµè§ä¸é¢çç¸å³æç« å¸æ大家以åå¤å¤æ¯æèæ¬ä¹å®¶ï¼

相关推荐