gorilla/websocket的chat示例代码简单分析

代码地址:https://github.com/gorilla/websocket/tree/main/examples/chat

文件包含:main.go、hub.go、client.go、home.html

main.go文件

Go 复制代码
func main() {
	flag.Parse()
	hub := newHub() // 实例化Hub
	go hub.run() // 使用chan处理 增删Hub的连接 和 广播消息

	http.HandleFunc("/", serveHome) // 访问home.html页面

    // 处理websocket
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		serveWs(hub, w, r)
	})

	server := &http.Server{
		Addr:              *addr,
		ReadHeaderTimeout: 3 * time.Second,
	}
	err := server.ListenAndServe()
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

hub.go文件

Go 复制代码
// 相当于连接池
type Hub struct {
	// Registered clients.
	clients map[*Client]bool // 存放所有websocket连接

	// Inbound messages from the clients.
	broadcast chan []byte // 广播消息

	// Register requests from the clients.
	register chan *Client // 添加websocket连接

	// Unregister requests from clients.
	unregister chan *Client // 删除websocket连接
}

// 实例化Hub
func newHub() *Hub {
	return &Hub{
		broadcast:  make(chan []byte),
		register:   make(chan *Client),
		unregister: make(chan *Client),
		clients:    make(map[*Client]bool),
	}
}

// 使用chan处理 增删Hub的连接 和 广播消息
func (h *Hub) run() {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true // 添加连接
		case client := <-h.unregister:
			if _, ok := h.clients[client]; ok {
				delete(h.clients, client) // 删除连接
				close(client.send)
			}
		case message := <-h.broadcast:
			for client := range h.clients {
				select {
				case client.send <- message: // 广播消息
				default:
					close(client.send)
					delete(h.clients, client)
				}
			}
		}
	}
}

client.go文件

Go 复制代码
// 连接
type Client struct {
	hub *Hub // 引用Hub

	// The websocket connection.
	conn *websocket.Conn // websocket连接

	// Buffered channel of outbound messages.
	send chan []byte // 消息发送chan
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
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 // 将消息发送到广播消息chan
	}
}

// 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: // 从连接的chan接收消息
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				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
			}
		}
	}
}

// serveWs handles websocket requests from the peer.
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

    // 初始化Client
	client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
	client.hub.register <- client

	// Allow collection of memory referenced by the caller by doing all work in
	// new goroutines.
	go client.writePump() // 写数据
	go client.readPump() // 读数据
}

其他chat参考:

https://github.com/android-coco/chat

https://github.com/GoLangFengShen/chat

https://www.golangblogs.com/read/im/date-2023.02.19.09.38.24

相关推荐
EasyDSS4 小时前
连锁扩张管控难?一套视频会议/直播/点播/集群对讲EasyDSS音视频云平台打通门店全流程数字化
网络·音视频·easydss
比兔代理6 小时前
游戏、直播、远程办公遇到动态IP怎么办?
网络
Lzg_na6 小时前
ARP 地址解析协议详解
网络·网络协议
北京盛世宏博6 小时前
通信异常深度排查:以太网温湿度设备 TCP 粘包、UDP 丢包根治方案
单片机·网络协议·tcp/ip·udp·以太网·传感器·温湿度
上海广测检测科技有限公司6 小时前
消费类智能联网设备安全分级国标征求意见要点解析:三级五维框架与跨境对标逻辑
网络·经验分享
QZ166560951597 小时前
金融数据流转监测排行:从看见到拦截的距离
网络·安全·网络安全·金融
paopaokaka_luck8 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
CCblandy9 小时前
HTTP 长连接 3 类故障实测:[WinError 10053]、99 个 TIME_WAIT、69 次多余握手,4 种对策怎么选
后端·python·网络协议·http·云原生·性能优化·架构
雪雪爱冲浪9 小时前
2026 年网络爬虫领域的Puppeteer vs. Playwright
爬虫·网络协议·测试工具·接口隔离原则
zxanz110 小时前
SSL 证书申请下来快吗?
网络·网络协议·ssl