从0开始的go+websocket构建五子棋对战系统
基本框架
直接照搬,不多解释。
dao为数据库处理层
po为实体类
middleware为中间件,cors处理跨域
app对request请求进行封装
router处理路由
service为服务层,进行数据处理逻辑
网络框架
使用gin作为整体的网络框架,文档在这里https://github.com/gin-gonic/gin
//main.go
func main() {
engine := gin.Default()
dao.Setup()
service.Setup()
router.Setup(engine)
err := engine.Run(fmt.Sprintf(":%v", "5521"))
if err != nil {
panic(err)
}
//user := po.User{1, "wxy", "2020"}
//service.Test(&user)
}
gin.default用来生成一个处理网络请求的实体。交由router进行处理
//router
func Setup(engine *gin.Engine) {
//处理cors
engine.Use(middleware.Cors())
//静态文件
//engine.Static("")
user := engine.Group("/user")
{
hub := service.ExUserService
user.POST("/test", app.HandlerFunc(hub.Test))
}
}
捕获路由并交给对应的函数(hub.test)处理。
数据持久化
使用gorm进行数据库管理。在dao中实现对应函数。例如存储一个用户信息方法实现如下
//dao/user.go
type UserDao struct {
Tx *gorm.DB
}
func (u UserDao) SaveUser(user *po.User) {
err := u.Tx.Save(user).Error
if err != nil {
panic(err)
}
}
在go中,会默认在将对象保存到类名(严格的讲这么说是不准确的,因为go并非面向对象语言)加s。如果一个user类的实体对象,保存后就会存储到users表中。
数据库的连接
db, err := gorm.Open(mysql.Open(fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local",
"username", "password", "1.1.1.1", 3306, "dbname")), &gorm.Config{})
其中username,password分别填数据库的用户名密码。1.1.1.1替换成数据库ip,3306为端口,dbname是库名。
websocket架构
使用melody处理websocket请求。melody是基于github.com/gorilla/websocket 抽象出的websocket处理框架。可以直接使用。
文档在这里https://pkg.go.dev/gopkg.in/olahol/melody.v1#section-readme
下载该依赖需要换源,方法问谷歌。
实例化一个melody对象,然后为其设置收到msg时候的处理方法。最后在路由中指向该对象即可由其接收websocket请求。
//websocket.go
func InitMelody() *melody.Melody {
m = melody.New()
m.HandleMessage(Receive)
return m
}
func Receive(s *melody.Session, msg []byte) {
m.Broadcast(msg)
}
//router.go
engine.GET("/ws", func(c *gin.Context) {
m.HandleRequest(c.Writer, c.Request)
})
上面的函数简单的进行了一个消息的复读。测试一下
然后测试一下由服务器主动发送数据。
加上对于用户连接和用户断开的处理逻辑
//websocket.go
func InitMelody() *melody.Melody {
m = melody.New()
m.HandleMessage(Receive)
m.HandleConnect(Connect)
m.HandleDisconnect(DisConnect)
return m
}
func Connect(s *melody.Session) {
id := uuid.NewV4().String()
idMap.Store(id, s)
s.Set("Id", id)
if !service.ExPlayerService.Connect(id, "unNamed") {
log.Print("此id已连接")
} else {
log.Print(id, "连接成功")
}
}
func DisConnect(s *melody.Session) {
id, ok := s.Get("Id")
fmt.Println(id, ok)
if ok == false {
return
}
service.ExPlayerService.DisConnect(id.(string))
idMap.LoadAndDelete(id)
fmt.Println(id, "断开连接")
}
用idMap保存从id到s的映射。方便此后根据id进行消息的传送
然后在service层加上对应player的处理逻辑
//service.player
func (p PlayerService) Connect(id string, name string) bool {
_, ok := maPlayer[id]
if ok {
return false
}
player := po.Player{
Id: id,
Name: name,
Color: -1,
}
maPlayer[id] = player
return true
}
func (p PlayerService) DisConnect(id string) bool {
_, ok := maPlayer[id]
if !ok {
return false
}
delete(maPlayer, id)
return true
}
持续更新中...