Skip to content

gorilla/websocket

github.com/gorilla/websocket

参考

Upgrader

Upgrader指定用于将 HTTP 连接升级到 WebSocket 连接

type Upgrader struct {
    HandshakeTimeout time.Duration
    ReadBufferSize, WriteBufferSize int
    WriteBufferPool BufferPool
    Subprotocols []string
    Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
    CheckOrigin func(r *http.Request) bool
    EnableCompression bool
}
  • HandshakeTimeout: 握手完成的持续时间
  • ReadBufferSizeWriteBufferSize:以字节为单位指定I/O缓冲区大小。如果缓冲区大小为零,则使用HTTP服务器分配的缓冲区
  • CheckOrigin : 函数应仔细验证请求来源 防止跨站点请求伪造

这里一般会设置下CheckOrigin来解决跨域问题,也可以var upgrader = websocket.Upgrader{} // use default options使用默认配置。

Conn

Conn类型表示WebSocket连接,这个结构体的组成包括两部分,写入字段(Write fields)和 读取字段(Read fields)

type Conn struct {
 conn        net.Conn
 isServer    bool
 ...

 // Write fields
 writeBuf      []byte        
 writePool     BufferPool
 writeBufSize  int
 writer        io.WriteCloser 
 isWriting     bool           
 ...
 // Read fields
 readRemaining int64
 readFinal     bool  
 readLength    int64 
 messageReader *messageReader 
 ...
}

isServer 字段来区分我们是否用Conn作为客户端还是服务端,也就是说说gorilla/websocket中同时编写客户端程序和服务器程序,但是一般是Web应用程序使用单独的前端作为客户端程序。

建立连接

服务端示例

以gin框架为例

var upgrader = websocket.Upgrader{} // use default options

func main() {
 router := gin.Default()
 router.GET("/ws", func(c *gin.Context) {
  wsHandler(c.Writer, c.Request)
 })
 router.Run("localhost:8080")
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
 //转换为升级为websocket
 conn, err := upGrader.Upgrade(w, r, nil)
 //释放连接
 defer conn.Close()

 for {
  //接收消息
  messageType, message, err := conn.ReadMessage()
  log.Println("server receive messageType", messageType, "message", string(message))
  //发送消息
  err = conn.WriteMessage(messageType, []byte("pong"))
 }
}

客户端示例

 //服务器地址 websocket 统一使用 ws://
 url := "ws://localhost:8080/ws"
 //使用默认拨号器,向服务器发送连接请求
 conn, _, err := websocket.DefaultDialer.Dial(url, nil)
 //关闭连接
 defer conn.Close()
 //发送消息
 go func() {
  for {
   err := conn.WriteMessage(websocket.BinaryMessage, []byte("ping"))
   time.Sleep(time.Second * 2)
  }
 }()
 //接收消息
 for {
  _, data, err := conn.ReadMessage()
  fmt.Println("client receive message: ", string(data))
 }

收发消息

for {
    messageType, p, err := conn.ReadMessage()
    if err != nil {
        log.Println(err)
        return
    }
    if err := conn.WriteMessage(messageType, p); err != nil {
        log.Println(err)
        return
    }
}
  • messageType: int,消息类型,值为 websocket.BinaryMessagewebsocket.TextMessage
  • p: []byte,消息内容。

也可以使用 io.Readerio.WriteCloser 收发消息:

for {
    messageType, r, err := conn.NextReader()
    if err != nil {
        return
    }
    w, err := conn.NextWriter(messageType)
    if err != nil {
        return err
    }
    if _, err := io.Copy(w, r); err != nil {
        return err
    }
    if err := w.Close(); err != nil {
        return err
    }
}

控制消息

func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error

func (c *Conn) SetCloseHandler(h func(code int, text string) error)
func (c *Conn) SetPingHandler(h func(appData string) error)
func (c *Conn) SetPongHandler(h func(appData string) error)

也可以正常使用WriteMessage()NextWriter()发送控制消息。

控制消息包括CloseMessagePingMessagePongMessage

可以通过SetHandler设置接收到控制消息时的回调,示例:

func Ping() {
    conn := GetConn()
    defer conn.Close()

    done := make(chan struct{})

    conn.SetPongHandler(func(appData string) error {
        done <- struct{}{}
        return nil
    })

    conn.WriteMessage(websocket.PingMessage, []byte("ping"))

    go func() {
        conn.ReadMessage()
    }()

    select {
    case <-time.After(time.Second * 5):
        fmt.Println("Server is dead: timeout")
        return
    case <-done:
        fmt.Println("Server is alive")
        return
    }
}