golang websocket包demo解读

126 阅读4分钟

websocket包demo解读

main

func serveHome(w http.ResponseWriter, r *http.Request) {
    log.Println(r.URL)
    if r.URL.Path != "/" {
        http.Error(w, "Not found", http.StatusNotFound)
        return
    }
    if r.Method != http.MethodGet {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    http.ServeFile(w, r, "home.html")
}

这是一个名为 serveHome 的函数,该函数将处理 HTTP 请求。当请求的 URL 不是根路径("/")时,它将返回一个 HTTP 状态码为 404 的错误。当请求的 HTTP 方法不是 GET 时,它将返回一个 HTTP 状态码为 405 的错误。否则,它将返回名为 home.html 的文件内容

func main() {
    flag.Parse()
    hub := newHub()
    go hub.run()
    http.HandleFunc("/", serveHome)
    http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
        serveWs(hub, w, r)
    })
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

这是程序的主函数。它首先调用 flag.Parse() 来解析命令行参数。然后它创建了一个 WebSocket hub 并在一个单独的 goroutine 中运行它。它还将两个 HTTP 请求处理函数与不同的路径关联起来:"/" 路径关联到 serveHome 函数,"/ws" 路径关联到 serveWs 函数。最后,它使用 http.ListenAndServe() 启动 HTTP 服务并监听 addr 变量指定的地址。如果出现错误,它将通过 log.Fatal() 输出错误信息并退出程序。

这段代码实现了一个简单的 WebSocket 聊天室,可以通过访问 http://localhost:8080 来访问。在聊天室中,你可以通过输入你的用户名和消息来发送聊天信息。

hub

func (c *Client) readPump() {
   defer func() {
      c.hub.unregister <- c
      c.conn.Close()
   }()
   c.conn.SetReadLimit(maxMessageSize)
   c.conn.SetReadDeadline(time.Now().Add(pongWait))
   c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
   for {
      _, message, err := c.conn.ReadMessage()
      if err != nil {
         if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
            log.Printf("error: %v", err)
         }
         break
      }
      message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
      c.hub.broadcast <- message
   }
}

这段代码定义了一个方法readPump(),用于将WebSocket连接中的消息推送到中央处理器(hub)。

在方法的开始,使用defer关键字定义了一个匿名函数,它将在方法结束时被调用。这个函数会将当前客户端从中央处理器的注册列表中删除,并关闭WebSocket连接。

接下来,设置了一些WebSocket连接的参数。首先,使用c.conn.SetReadLimit()设置了消息的最大大小,然后使用c.conn.SetReadDeadline()设置了读取消息的截止时间,也就是在超过这个时间后,如果没有读取到消息,就会关闭连接。最后,使用c.conn.SetPongHandler()设置了接收pong消息的处理函数,如果收到了pong消息,会更新读取消息的截止时间。

在进入无限循环后,使用c.conn.ReadMessage()从WebSocket连接中读取消息。如果在读取过程中发生了错误,就会跳出循环。如果错误是websocket.IsUnexpectedCloseError()返回true,也就是客户端意外关闭了连接,就会记录一个错误日志。否则,认为是正常的连接关闭,并跳出循环。

如果读取消息成功,将消息去除前后的空格和换行符,然后将其推送到中央处理器的广播通道中,以便其他客户端可以接收到这个消息。

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
   ticker := time.NewTicker(pingPeriod)
   defer func() {
      ticker.Stop()
      c.conn.Close()
   }()
   for {
      select {
      case message, ok := <-c.send:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if !ok {
            // The hub closed the channel009 
            +6+.
            c.conn.WriteMessage(websocket.CloseMessage, []byte{})
            return
         }

         w, err := c.conn.NextWriter(websocket.TextMessage)
         if err != nil {
            return
         }
         w.Write(message)

         // Add queued chat messages to the current websocket message.
         n := len(c.send)
         for i := 0; i < n; i++ {
            w.Write(newline)
            w.Write(<-c.send)
         }

         if err := w.Close(); err != nil {
            return
         }
      case <-ticker.C:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
            return
         }
      }
   }
}

这是一个调用的函数,负责将消息从集线器写入 websocket 连接。writePump

该函数首先创建一个 以固定间隔向语句发送信号。该函数还设置一个语句,该语句在函数返回时停止代码并关闭连接。ticker``select``defer

然后,该函数使用侦听两个通道的语句启动无限循环:和 .通道是要写入连接的消息通道,通道以固定的间隔发送信号。select``c.send``ticker.C``c.send``ticker.C

如果在通道上收到消息,该函数会在连接上设置写入截止时间并将消息写入连接。如果通道中有更多消息,该函数将写入换行符,后跟下一条消息,直到发送完所有消息。然后,该函数关闭返回的 。c.send``c.send``Writer``NextWriter()

如果通道上收到信号,该函数会在连接上设置写入截止时间,并向连接发送 ping 消息。ticker.C

如果连接出现错误,函数将返回并执行语句,从而停止代码并关闭连接。defer

总体而言,此函数通过执行来自此 goroutine的所有写入来确保连接只有一个写入器。它还处理编写排队的聊天消息和发送 ping 消息以保持连接活动。