在线聊天室
项目简介
基于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
},
}
ReadBufferSize
和WriteBufferSize
:分别指定了读取和写入 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 消息。结构体的字段Username
和Message
分别对应 JSON 数据中的username
和message
字段。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")
}