使用Go语言创建WebSocket服务的实现示例
ä»å¤©ä»ç»å¦ä½ç¨ 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 ç¸æ¯ï¼å·æè®¸å¤ä¼ç¹ï¼
- è½»é级æ¥å¤´åå°äºæ°æ®ä¼ è¾å¼éã
- å个Web客æ·ç«¯ä»éè¦ä¸ä¸ªTCPè¿æ¥ã
- WebSocketæå¡å¨å¯ä»¥å°æ°æ®æ¨éå°Web客æ·ç«¯ã
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/echoEchoåºç¨çWebSocket æå¡çè·¯ç±/ws/echo_displayEchoåºç¨ç客æ·ç«¯é¡µé¢çè·¯ç±ã å建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 å容请æç´¢èæ¬ä¹å®¶ä»¥åçæç« æç»§ç»æµè§ä¸é¢çç¸å³æç« 叿大家以åå¤å¤æ¯æèæ¬ä¹å®¶ï¼