WebSocket 介紹

websocket 提供一種在client與server間全雙工通訊的方式。其通訊協定可參考:RFC 6455

通訊原理

websocket 通訊方式
Client Request
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
Server side Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

WebSocket 分類

  • Datagram sockets (SOCK_DGRAM):使用UDP協定
  • Stream sockets (SOCK_STREAM):使用TCP、SCTP、DCCP
  • Raw sockets (or raw IP sockets)

記憶體使用量

如果照正常程式設計,要服務百萬個連線,大約要20GB左右的記憶體。因此必須要對程式做最佳化,以免記憶體使用過大。

  • goroutines
  • select / poll:定期踢掉不在線上或斷線的使用者連線。(syscall.Timeval, syscall.Select)
  • epoll

Go 語言使用函數

基本上Go提供:”golang.org/x/net/websocket” 供使用者使用。不過大多使用者會使用:”github.com/gorilla/websocket” 提供比較完整的使用方式。

server.go 範例
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(r *http.Request) bool { return true },
}

func homePage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Home page")
}

func reader(conn *websocket.Conn) {
	for {
		messageType, p, err := conn.ReadMessage()
		if err != nil {
			log.Println(err)
			return
		}
		log.Println(string(p))
		if err := conn.WriteMessage(messageType, p); err != nil {
			log.Println(err)
			return
		}
	}
}

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
	upgrader.CheckOrigin = func(r *http.Request) bool { return true }

	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("Client Successfully connected...")
	reader(ws)
}

func setRoutes() {
	http.HandleFunc("/", homePage)
	http.HandleFunc("/ws", wsEndpoint)
}

func main() {
	setRoutes()
	log.Fatal(http.ListenAndServe(":8080", nil))
}
index.html
<!DOCTYPE html>
<html lang="zh_TW">
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Ws Example</title>
   </head>
   <body>
      <script>
         let socket = new WebSocket("ws://localhost:8080/ws");
         
         socket.onopen = function() {
            socket.send("Hi from the client.");
         };

         socket.onmessage = function(msg) {
            console.log(msg);
         };

         socket.onclose = function(event) {
         };

         socket.onerror = function(error) {
            console.log("error", error);
         }
      </script>
   </body>
</html>
Client 範例
 // create connection
    // schema can be ws:// or wss://
    // host, port – WebSocket server
    conn, err := websocket.Dial("{schema}://{host}:{port}", "", op.Origin)
    if err != nil {
        // handle error
    } 
    defer conn.Close()
             .......
      // send message
        if err = websocket.JSON.Send(conn, {message}); err != nil {
         // handle error
    }
              .......
        // receive message
    // messageType initializes some type of message
    message := messageType{}
    if err := websocket.JSON.Receive(conn, &message); err != nil {
          // handle error
    }  
        .......

常用函數庫

參考資料