几个月前,把java分布式游戏后端研究了一遍,仿照现成的架构写了两套游戏出来。但是我的脚步并未停下,现在开始研究如何用Go语言搭建游戏后端。
1、设计前后端交互的方式
之前的java我用到了netty+protobuf和前端进行交互,而游戏服务之间采用RPC(远程过程调用)的方式进行通信。netty是java网络编程中一种比较优秀的网络通信框架,采用的是select事件驱动模式,这种轮询的方式尽管有些落后,但是相比于同步NIO的通信方式,性能还是有大幅度的提升。
在go中,一般使用net/http来建立和远程客户端的通信,搭配webSocket来接收并处理远程的请求。一个用net/http来搭建服务器的例程如下:
package main
import (
"fmt"
"net/http"
"log"
)
func HandleIndex(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Println("PATH: ", r.URL.Path)
fmt.Println("SCHEME: ", r.URL.Scheme)
fmt.Println("METHOD: ", r.Method)
fmt.Println()
fmt.Fprintf(w, "<h1>Index Page</h1>")
}
func main() {
http.HandleFunc("/", HandleIndex)
err := http.ListenAndServe(":8000", nil)
if err != nil {
log.Fatal("ERROR: ", err)
}
}
我把这段代码解析一下:
http.HandleFunc("/", HandleIndex)
//注册处理类,即表示如果收到根目录的请求,就使用HandleIndex这个方法来解决
http.ListenAndServe(":8000", nil)
//服务监听8000端口
用go搭建一个demo服务器博客入口
2、包net/http中嵌入websocket包的方法:
http.Handle("/xss/", websocket.Server{Handler: s.handleClient, Handshake: nil})
确定了前后端走通过http和websocket之后,可以使用mux包来定义处理路由
//导入方法:
imposrt "github.com/gorilla/mux"
//使用方法:
r := mux.NewRouter()
http.Handle("/", r)
//定义mux
func registerHttpHandlers(r *mux.Router) {
r.HandleFunc("/v123/plat/userInfo", web.WebUserInfoHandler)
r.HandleFunc("/v123/plat/userCharge", web.WebChangeCardHandler)
}
//需要使用路由的时候只需要调用
registerHttpHandlers(r)
//就可以了
除了显式的调用路由处理的方法来预定义处理类,还可以定义数据包的格式来接收和处理前端的请求。首先,定义一个枚举类型来表示消息类型:
const (
MessageId_LOGIN = 1 //登录
MessageId_HEART_BEAT = 2 //心跳
MessageId_RECONNECT = 3 //重连
MessageId_CREATE_ROOM = 4 //创建房间
MessageId_ENTER_ROOM = 5 //进入房间
MessageId_ENTER_ROOM_NOTIFY = 6 //进入房间通知
MessageId_BACK_ROOM = 7 //返回房间信息
MessageId_DISMISS_ROOM_REQ = 8 //解散房间请求
MessageId_DISMISS_ROOM_NOTIFY = 9 //解散请求房间广播
MessageId_DISMISS_ROOM_USER_RESULT = 10 //其他玩家的处理结果
MessageId_DISMISS_ROOM_USER_RESULT_NOTIFY = 11 //其他玩家的处理结果广播
MessageId_DISMISS_ROOM_RESULT_NOTIFY = 12 //解散房间成功通知
MessageId_LEAVE_ROOM = 13 //退出房间
MessageId_LEAVE_ROOM_NOTIFY = 14 //退出房间广播
MessageId_BACK_ROOM_NOTIFY = 15 //返回房间广播
MessageId_START_GAME = 16 //游戏开始
...
)
定义了消息类型之后,就可以来注册处理类了,可以自行设计一个集合,以键值对的方式,将消息类型的枚举值作为key,而处理方法作为value,每次接收到message后可以自动响应出解决的方法。
func registerUserHandlers() {
GetMsgRegistry().RegisterUnLoginMsg(int32(util.MessageId_LOGIN))
GetMsgRegistry().RegisterUnLoginMsg(int32(util.MessageId_WECHAT_LOGIN))
GetMsgRegistry().RegisterUnLoginMsg(int32(util.MessageId_ACCESSTOKEN_LOGIN))
GetMsgRegistry().RegisterMsg(int32(util.MessageId_LOGIN), user.LoginHandler)
GetMsgRegistry().RegisterMsg(int32(util.MessageId_CREATE_ROOM), user.CreateRoomHandler)
GetMsgRegistry().RegisterMsg(int32(util.MessageId_ENTER_ROOM), user.EnterRoomHandler)
GetMsgRegistry().RegisterMsg(int32(util.MessageId_BACK_ROOM), user.BackRoomHandler)
}
然后等到收到前端的请求之后就可以使用类似于
func (registry *MsgRegistry) getHandler(msgId int32) func(msg *server.ClientMsg, sess *server.Session) []byte {
registry.mu.RLock()
defer registry.mu.RUnlock()
fmt.Print(msgId)
fmt.Print("\n")
return registry.registry[msgId]
}
这样的方法取出来。这样设计并接收前端请求的架构就已经搭建好了。