LOADING

加载过慢请开启缓存 浏览器默认开启

chat_room

2025/2/4 项目

在线聊天室

项目简介

​ 基于websocket的简单在线聊天室,仅实现实时接受文字并返回所有客户端的功能,没有添加注册与登录,发送消息时的名字可以随时自己改变。

项目内容

数据库相关

使用mysql存储用户的发出的信息以及发出者的信息

  • Init
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func InitDB() {
    var err error

    db, err = sql.Open("mysql", "root:********@tcp(127.0.0.1:3306)/chatroom?parseTime=true")
    if err != nil {
        panic(err.Error())
    }
    err = db.Ping()
    if err != nil {
        panic(err.Error())
    }
    fmt.Println("Successfully connected to mysql")

}
  • 存和出
package main

import (
    "database/sql"
    "fmt"
)

type Message struct {
    ID       int
    Username string
    Message  string
}

func saveMessage(db *sql.DB, username, message string) error {
    stmt, err := db.Prepare("INSERT INTO message(username,message) VALUES(?,?)")
    if err != nil {
        return err
    }
    defer stmt.Close()
    _, err = stmt.Exec(username, message)
    if err != nil {
        return err
    }
    fmt.Printf("message saved: %s-%s\n", username, message)
    return nil
}

func getMessage(db *sql.DB) ([]Message, error) {
    rows, err := db.Query("SELECT id,username,message FROM messages")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var messages []Message
    for rows.Next() {
        var msg Message
        err := rows.Scan(&msg.ID, &msg.Username, &msg.Message)
        if err != nil {
            return nil, err
        }
        messages = append(messages, msg)
    }
    if err = rows.Err(); err != nil {
        return nil, err
    }
    return messages, nil

}

websocket相关

定义 WebSocket 升级器

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}
  • ReadBufferSizeWriteBufferSize:分别指定了读取和写入 WebSocket 消息时使用的缓冲区大小。
  • CheckOrigin:是一个函数,用于检查客户端请求的来源是否允许。这里简单地返回 true,表示允许所有来源的请求。

定义连接列表

var connections = make([]*websocket.Conn, 0)
  • connections 是一个切片,用于存储所有连接到服务器的 WebSocket 连接。每个元素是一个指向 websocket.Conn 类型的指针,该类型表示一个 WebSocket 连接。

处理 WebSocket 连接的函数

func serveWs(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    defer conn.Close()

    connections = append(connections, conn)
  • serveWs 函数用于处理客户端的 WebSocket 连接请求。
  • upgrader.Upgrade(w, r, nil):将 HTTP 连接升级为 WebSocket 连接。如果升级成功,返回一个 websocket.Conn 类型的连接对象;如果失败,返回错误信息。
  • defer conn.Close():确保在函数返回时关闭 WebSocket 连接。
  • connections = append(connections, conn):将新的 WebSocket 连接添加到 connections 切片中。

循环读取客户端消息

    for {
        var msg struct {
            Username string `json:"username"`
            Message  string `json:"message"`
        }
        err := conn.ReadJSON(&msg)
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Println("error:", err)
            }
            for i, c := range connections {
                if c == conn {
                    connections = append(connections[:i], connections[i+1:]...)
                    break
                }
            }
            break
        }
  • var msg struct {...}:定义一个匿名结构体,用于存储从客户端接收到的 JSON 消息。结构体的字段 UsernameMessage 分别对应 JSON 数据中的 usernamemessage 字段。
  • conn.ReadJSON(&msg):从 WebSocket 连接中读取 JSON 数据,并将其解析到 msg 结构体中。
  • 如果读取过程中出现错误,检查是否是意外关闭错误(如客户端异常断开连接)。如果是,则记录错误信息,并从 connections 切片中移除该连接。

保存消息到数据

        //保存消息到数据库
        err = saveMessage(db, msg.Username, msg.Message)
        if err != nil {
            log.Println(err)
        }
  • saveMessage 函数用于将接收到的消息保存到数据库中。但代码中 db 变量未定义,需要先建立数据库连接并初始化 db。同时,saveMessage 函数也未给出实现,需要自行实现该函数。

广播消息给所有连接的客户端

        //广播消息给所有连接的客户端
        for _, c := range connections {
            err = c.WriteJSON(msg)
            if err != nil {
                log.Println(err)
                for i, con := range connections {
                    if con == c {
                        connections = append(connections[:i], connections[i+1:]...)
                        break
                    }
                }
            }
        }
    }
}
  • c.WriteJSON(msg):将接收到的消息以 JSON 格式发送给每个连接的客户端。
  • 如果发送过程中出现错误,记录错误信息,并从 connections 切片中移除该连接。

API接口

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func SetupRoutes(r *gin.Engine) {
    r.GET("/ws", func(c *gin.Context) {
        serveWs(c.Writer, c.Request)
    })

    r.GET("/messages", func(c *gin.Context) {
        message, err := getMessage(db)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": "尝试从服务器获取数据时发生了错误",
            })
            return
        }
        c.JSON(http.StatusOK, message)
    })
}

主函数

package main

import "github.com/gin-gonic/gin"

func main() {
    InitDB()
    r := gin.Default()
    SetupRoutes(r)
    r.Run(":8080")
}