酷Go推荐 Golang 官方认可的 websocket 库-gorilla/websocket

wentao · 2021年07月15日 · 886 次阅读
本帖已被设为精华帖!

推荐理由

Golang 官方标准库实现的 websocket 在功能上有些欠缺,本次介绍的 gorilla/websocket 库,是Gorilla出品的速度快、质量高,并且被广泛使用的 websocket 库,很好的弥补了标准库功能上的欠缺。另外 Gorilla Web toolkit 包含多个实用的 HTTP 应用相关的工具库,感兴趣可以到官网主页https://www.gorillatoolkit.org自取。

功能介绍

gorilla/websocket 库是 RFC 6455 定义的 websocket 协议的一种实现,在数据收发方面,提供 Data Messages、Control Messages 两类 message 粒度的读写 API;性能方面,提供 Buffers 和 Compression 的相关配置选项;安全方面,可通过 CheckOrigin 来控制是否支持跨域。

gorilla/websocket 库和官方实现的对比

摘自 gorilla GitHub 主页

github.com/gorilla golang.org/x/net
RFC 6455 Features
Passes Autobahn Test Suite Yes No
Receive fragmented message Yes No, see note 1
Send close message Yes No
Send pings and receive pongs Yes No
Get the type of a received data message Yes Yes, see note 2
Other Features
Compression Extensions Experimental No
Read message using io.Reader Yes No, see note 3
Write message using io.WriteCloser Yes No, see note 3

Notes:

  1. Large messages are fragmented in Chrome's new WebSocket implementation.
  2. The application can get the type of a received data message by implementing a Codec marshal function.
  3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. Read returns when the input buffer is full or a frame boundary is encountered. Each call to Write sends a single frame message. The Gorilla io.Reader and io.WriteCloser operate on a single WebSocket message.

使用指南

安装

go get github.com/gorilla/websocket

基础示例

下面以一个简单的 echo 来说明 gorilla/websocket 库基本使用。

client 代码:

package main

import (
    "flag"
    "log"
    "net/url"
    "os"
    "os/signal"
    "time"

    "github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

func main() {
    flag.Parse()
    log.SetFlags(0)

    // 用来接收命令行的终止信号
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)

    // 和服务端建立连接
    u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
    log.Printf("connecting to %s", u.String())

    c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        log.Fatal("dial:", err)
    }
    defer c.Close()

    done := make(chan struct{})

    go func() {
        defer close(done)
        for {
            // 从接收服务端message
            _, message, err := c.ReadMessage()
            if err != nil {
                log.Println("read:", err)
                return
            }
            log.Printf("recv: %s", message)
        }
    }()

    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            // 向服务端发送message
            err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
            if err != nil {
                log.Println("write:", err)
                return
            }
        case <-interrupt:
            log.Println("interrupt")

            // 收到命令行终止信号,通过发送close message关闭连接。
            err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
            if err != nil {
                log.Println("write close:", err)
                return
            }
            // 收到接收协程完成的信号或者超时,退出
            select {
            case <-done:
            case <-time.After(time.Second):
            }
            return
        }
    }
}

server 代码:

package main

import (
    "flag"
    "html/template"
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var addr = flag.String("addr", "localhost:8080", "http service address")

var upgrader = websocket.Upgrader{}

func echo(w http.ResponseWriter, r *http.Request) {
    // 完成和Client HTTP >>> WebSocket的协议升级
    c, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Print("upgrade:", err)
        return
    }
    defer c.Close()
    for {
        // 接收客户端message
        mt, message, err := c.ReadMessage()
        if err != nil {
            log.Println("read:", err)
            break
        }
        log.Printf("recv: %s", message)
        // 向客户端发送message
        err = c.WriteMessage(mt, message)
        if err != nil {
            log.Println("write:", err)
            break
        }
    }
}

func main() {
    flag.Parse()
    log.SetFlags(0)
    http.HandleFunc("/echo", echo)
    log.Fatal(http.ListenAndServe(*addr, nil))
}

更多示例可以参考 https://github.com/gorilla/websocket/tree/master/examples

总结

gorilla/websocket 库是 websocket 协议的一种实现,相比标准库的实现,封装了协议细节,使用者关注 message 粒度的 API 即可,但需要注意 message 的读写 API 非并发安全,使用时注意不要多个协程并发调用。

参考资料

  1. https://github.com/gorilla/websocket
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
astaxie 将本帖设为了精华贴 07月16日 01:03
cczsunnyman GoCN 每日新闻 (2021-07-16) 中提及了此贴 07月16日 02:02
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册