chatByGin
chatByGin
此项目是将官方 WebSocket 文档下的 Chat 示例项目 用 Gin 框架 MVC 架构实现。
项目结构
chatByGin/
│ go.mod
│ go.sum
│ main.go
├─controllers
│ chat.go
├─models
│ client.go
│ hub.go
├─routers
│ chat.go
└─views
chat.html
各模块功能说明
1. 主入口 (main.go)
package main
import (
"chatByGin/models"
"chatByGin/routers"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("views/*") // 加载HTML模板
hub := models.NewHub() // 创建Hub中心
go hub.Run() // 启动Hub消息处理
routers.ChatIndexInit(hub, r) // 初始化路由
r.Run(":9090") // 启动服务器
}
功能:
- 初始化Gin框架
- 创建并启动Hub中心
- 设置路由
- 启动HTTP服务器
2. 控制器层 (controllers/chat.go)
package controllers
import (
"chatByGin/models"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
// 渲染聊天页面
func ChatIndexInit(c *gin.Context) {
c.HTML(http.StatusOK, "chat.html", gin.H{})
}
// 处理WebSocket连接
func ServerWs(hub *models.Hub, c *gin.Context) {
conn, err := models.Upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Println("WebSocket Upgrade error: ", err)
return
}
client := &models.Client{
Hub: hub,
Conn: conn,
ChSend: make(chan []byte, 256),
}
hub.ChRegister <- client // 注册客户端
go client.WritePump() // 启动写协程
go client.ReadPump() // 启动读协程
}
功能:
ChatIndexInit: 渲染聊天页面ServerWs:- 升级HTTP连接为WebSocket
- 创建并注册客户端
- 启动读写协程
3. 模型层 (models)
hub.go
package models
type Hub struct {
ClientList map[*Client]bool // 客户端列表
ChBroadCast chan []byte // 广播消息通道
ChRegister chan *Client // 注册通道
ChUnregister chan *Client // 注销通道
}
func NewHub() *Hub {
return &Hub{
ClientList: make(map[*Client]bool),
ChBroadCast: make(chan []byte),
ChRegister: make(chan *Client),
ChUnregister: make(chan *Client),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.ChRegister:
h.ClientList[client] = true // 注册客户端
case client := <-h.ChUnregister:
if _, ok := h.ClientList[client]; ok {
delete(h.ClientList, client) // 注销客户端
close(client.ChSend) // 关闭发送通道
}
case message := <-h.ChBroadCast:
for client := range h.ClientList {
select {
case client.ChSend <- message: // 发送消息
default:
close(client.ChSend) // 发送失败则关闭连接
delete(h.ClientList, client)
}
}
}
}
}
功能:
- 管理所有客户端连接
- 处理客户端注册/注销
- 广播消息到所有客户端
client.go
package models
import (
"bytes"
"log"
"time"
"github.com/gorilla/websocket"
)
// WebSocket配置
var Upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
maxMessageSize = 512
)
type Client struct {
Hub *Hub
Conn *websocket.Conn
ChSend chan []byte
}
// 读取消息并广播
func (c *Client) ReadPump() {
defer func() {
c.Hub.ChUnregister <- 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 {
log.Println("ReadMessage Error", err)
break
}
message = bytes.TrimSpace(bytes.Replace(message, []byte{'\n'}, []byte{' '}, -1))
c.Hub.ChBroadCast <- message
}
}
// 发送消息到客户端
func (c *Client) WritePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.Conn.Close()
}()
for {
select {
case message, ok := <-c.ChSend:
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.Conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
w.Write(message)
n := len(c.ChSend)
for i := 0; i < n; i++ {
w.Write([]byte{'\n'})
w.Write(<-c.ChSend)
}
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
}
}
}
}
功能:
ReadPump: 从WebSocket读取消息并广播WritePump: 从Hub接收消息并写入WebSocket- 实现心跳机制保持连接
4. 路由层 (routers/chat.go)
package routers
import (
"chatByGin/controllers"
"chatByGin/models"
"github.com/gin-gonic/gin"
)
func ChatIndexInit(hub *models.Hub, c *gin.Engine) {
ChatRouter := c.Group("/chat")
{
ChatRouter.GET("", controllers.ChatIndexInit) // 聊天页面
ChatRouter.GET("/ws", func(c *gin.Context) { // WebSocket连接
controllers.ServerWs(hub, c)
})
}
}
功能:
- 设置聊天相关路由
- 将页面请求和WebSocket请求分组管理
5. 视图层 (views/chat.html)
<!DOCTYPE html>
<html>
<head>
<title>聊天示例</title>
<script>
window.onload = function() {
var conn = new WebSocket("ws://" + document.location.host + "/chat/ws");
var msg = document.getElementById("msg");
var log = document.getElementById("log");
conn.onmessage = function(evt) {
var item = document.createElement("div");
item.innerText = evt.data;
log.appendChild(item);
log.scrollTop = log.scrollHeight;
};
document.getElementById("form").onsubmit = function() {
conn.send(msg.value);
msg.value = "";
return false;
};
};
</script>
<style>
/* 样式代码 */
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="发送" />
<input type="text" id="msg" size="64" autofocus />
</form>
</body>
</html>
功能:
- 提供聊天界面
- 建立WebSocket连接
- 发送和显示消息
数据流分析
-
客户端连接流程:
- 用户访问
/chat→ 渲染HTML页面 - 页面 JS 建立 WebSocket 连接到
/chat/ws - 服务端创建 Client 并注册到 Hub
- 启动读写协程
- 用户访问
-
消息广播流程:
- 客户端发送消息 → ReadPump接收 → 广播到 Hub
- Hub → 所有 Client 的 WritePump → 发送到各自 WebSocket 连接
-
心跳机制:
- WritePump定期发送Ping
- ReadPump设置Pong处理器保持连接

浙公网安备 33010602011771号