GoLang WebSocket 编程

2020-07-28 language network golang

简介

在 GoLang 中可以通过 golang.org/x/net/websocket 包使用 WebSocket 功能,如下是一个简单的示例,包含了两个处理 URI ,其中 / 会返回主页的模板,然后通过 /upper 将字符串转换为大写。

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"
	"strings"

	"golang.org/x/net/websocket"
)

func upper(ws *websocket.Conn) {
	var err error
	for {
		var reply string

		if err = websocket.Message.Receive(ws, &reply); err != nil {
			fmt.Println(err)
			break
		}

		if err = websocket.Message.Send(ws, strings.ToUpper(reply)); err != nil {
			fmt.Println(err)
			break
		}
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		return
	}

	t, _ := template.ParseFiles("index.html")
	t.Execute(w, nil)
}

func main() {
	http.Handle("/upper", websocket.Handler(upper))
	http.HandleFunc("/", index)

	if err := http.ListenAndServe(":9999", nil); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

对应的 HTML 模板 index.html 为。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Websocket</title>
</head>
<body>
    <h1>String to Upper</h1>
    <form><p>Input : <input id="content" type="text" placeholder="Some String Here."></p></form>
    <label id="result">Result:</label><br><br>
    <button onclick="send()">Change</button>

    <script type="text/javascript">
        var sock = new WebSocket("ws://127.0.0.1:9999/upper");
        sock.onmessage = function(e) {
            var result = document.getElementById('result');
            result.innerHTML = "Result:" + e.data;
        }
        function send() {
            var msg = document.getElementById('content').value;
            sock.send(msg);
        }
    </script>
</body>
</html>

参数配置

TLS/SSL

与大多数的 GoLang 网络编程类似,在监听网络端口时,可以选择是否使用 TLS ,两种方式如下。

log.Fatal(http.ListenAndServe(":8080", nil))
log.Fatal(http.ListenAndServeTLS(":8080", "../pki/SVR/cert.pem", "../pki/SVR/key.pem", nil))

通过 log.Fatal 可以在发生异常的时候直接退出。

修改参数

最常见的参数如读写缓冲区,如下代码中,会通过 upgrader.Upgrade() 将协议从 HTTP 切换到 WebSocket ,而这里的 upgrader 实际上就是通过如下代码定义的,其中包含了具体的配置参数。

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

定制化

有时候在切换到 WebSocket 协议之前,我们希望通过最初的 HTTP 协议再获取一些参数信息,例如检查是否同源 (checkSameOrigin)、子协议的版本号 (selectSubprotocol)、鉴权信息等等,也就是在转换前进行检查。

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func ServeWebSocket(w http.ResponseWriter, r *http.Request) {
	hdr := http.Header{}

	subproto := r.Header.Get("Sec-Websocket-Protocol")
	log.Printf("got websocket sub-protocol '%s'.\n", subproto)

	for _, name := range websocket.Subprotocols(r) {
		log.Printf("Got subprotocols '%s'.\n", name);
		// check and choose a protocol.
		hdr.Set("Sec-Websocket-Protocol", name);
		break
	}

	ws, err := upgrader.Upgrade(w, r, hdr)
	if err != nil {
		log.Println("upgrade websocket failed,", err)
		return
	}
	defer ws.Close()

	log.Println("got websocket connection from", ws.RemoteAddr())
	ws.SetPingHandler(cli.pingHandler)

	// handle your own business.
}

func main() {
	http.HandleFunc("/echo", ServeWebSocket)
	//log.Fatal(http.ListenAndServe(":8080", nil))
	log.Fatal(http.ListenAndServeTLS(":8080", "../pki/SVR/cert.pem", "../pki/SVR/key.pem", nil))
}

在处理完后,通过 upgrader.Upgrade() 升级到 WebSocket 协议。

Ping

如上,可以通过 ws.SetPingHandler(pingHandler) 注册 Ping 的回调函数,该函数定义为。

func pingHandler(s string) error {
	log.Println("get ping", s)

	if err := c.ws.WriteMessage(websocket.TextMessage, []byte("string")); err != nil {
		return err
	}

	if err := c.ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
		return err
	}

	return nil
}

注意,上述的方式会发送两个报文。