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 消息以保持连接活动。