从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)
	})

上面的函数简单的进行了一个消息的复读。测试一下

image-20230412033950948

然后测试一下由服务器主动发送数据。

image-20230412034450305

加上对于用户连接和用户断开的处理逻辑

//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
}

持续更新中...

posted @ 2023-04-12 03:16  wxyww  阅读(84)  评论(0编辑  收藏  举报