本文章仅记录使用 Golang + Websocket 开发
网页对战游戏
项目时遇到的问题及解决方案。
项目地址: WebGame2。
该项目目前未使用任何GL
相关API,只是使用最简单的HTML+CSS
技术完成。
开发环境
后端
- 主开发语言
go
(http://docscn.studygolang.com/doc/) - 数据库插件
github.com/mattn/go-sqlite3
(github.com/mattn/go-sqlite3)
前端
- angular 8 (https://angular.cn/)
- ng-zorro-antd (https://ng.ant.design/docs/introduce/zh)
界面简单展示
页面切换没有记录在内,可以通过左上角登录信息
观察变化。测试时已经在另一个页面登录第二个账号。
文件结构
主要结构说明
+---- [ProjectPath]
| ---- bin // 打包可执行程序目录
| ---- WebGame.exe
| ---- db // 程序数据目录
| ---- [yyyyMD] // 日志文件
| ---- upload // 上传目录
| ---- WebGame.db // sqlite3 日志文件
| ---- src // go源文件目录
| ---- task // 消息推送包, 配合 WsService.sendTaskData() 使用
| ---- games // 游戏开发包
| ---- wzq // 五子棋游戏包
| ---- ws // websocket处理包
| ---- main // mainbao, 程序入口包
| ---- ui // 前端代码(angular 8)目录
| ---- WebGame.ini // 配置文件
踩坑&填坑
每个连接对象保存的数据被共享了?
连接监听, http.Handle()
处理的每个 websocket.Conn
都在新协程中处理
// WsService.go
http.Handle(conf.Cfgr.Ws.Url, websocket.Handler(func(conn *websocket.Conn) {
ch := make(chan int)
go handleConn(conn, ch)
<-ch
}))
原来的代码
// WsService.go
var WsClient = &wsClient{}
type wsClient struct {
// ...
conn *websocket.Conn
id string // 每个连接唯一标识
}
http.Handle(conf.Cfgr.Ws.Url, websocket.Handler(func(conn *websocket.Conn) {
go handleConn(conn)
}))
func handleConn(conn *websocket.Conn) {
id, _ := uuid.NewV4() // 为每个客户端产生唯一标识
WsClient.conn = conn
WsClient.id = string(id)
...
}
测试结果发现 WsClient
是一个共享的 *wsClient
,后来又测试不用指针创建 var WsClient = wsClient{}
任然出现相同的问题:WsClient.id
只能保存最后一个值,即使是子业务对象也是一样。
实验结果表明:协程
并不会重复执行&wsClient{}
或wsClient{}
或new(wsClient{})
这样的代码,需要手动完成创建对象的动作:
// WsService.go
const (
BOX_SIZE = 9 // 格子大小
WIN_HITS_COUNT = 5 // 胜利条件: 连线个数
)
func handleConn(conn *websocket.Conn, ch chan int) {
ws := &wsClient{} // 这里必须使用新的对象
// ...
ws.gameWzq = new(wzq.GameWzq) // 每个子游戏服务必须使用各自的对象(除非该服务没有保存状态)
// ...
}
同时由于这个特性,正好可以用在游戏房间
管理上:
// games/wzq/roomMgr.go
var RoomMgr = &mgr{
games: make(map[int]*gameA),
}
// 管理类
type mgr struct {
mu sync.Mutex
games map[int]*gameA // 所有五子棋游戏
}
// 五子棋游戏
type gameA struct {
rooms map[int]*room // 所有房间
}
// 游戏房间
type room struct {
No int `json:"no"`
P1 model.Account `json:"p1"`
P2 model.Account `json:"p2"`
playingId int // 可落子玩家
stones [BOX_SIZE][BOX_SIZE]int
}
至此问题得到解决。