最開始接觸到Leaf颜武,就是被它的網(wǎng)絡(luò)消息功能吸引的璃搜。那么先看看這部分功能吧。從文檔中得知:
Leaf 可以單獨(dú)使用 TCP 協(xié)議或 WebSocket 協(xié)議鳞上,也可以同時(shí)使用兩者这吻,換而言之,服務(wù)器可以同時(shí)接受 TCP 連接和 WebSocket 連接篙议,對開發(fā)者而言消息來自 TCP 還是 WebSocket 是完全透明的唾糯。
一、network和gate
這個(gè)功能在源碼中是如何實(shí)現(xiàn)的呢,看看network目錄下tcp開頭的移怯,和ws開頭的拒名,有xx_conn,xx_msg,xx_server,正好各有3個(gè)文件芋酌。在conn.go里有個(gè)Conn interface,所以xx_conn肯定是實(shí)現(xiàn)這個(gè)接口的兩個(gè)不同類型雁佳。按照這個(gè)思路脐帝,順便看一下processor.go里的解析器接口,也是有json.go和protobuf.go兩種實(shí)現(xiàn)糖权。
type Conn interface {
ReadMsg() ([]byte, error)
WriteMsg(args ...[]byte) error
LocalAddr() net.Addr
RemoteAddr() net.Addr
Close()
Destroy()
}
1.gate目錄
然后xx_conn這兩種連接方式堵腹,要對外透明,是封裝在gate包下面星澳,一起使用的疚顷。先看一下agent.go:
type Agent interface {
WriteMsg(msg interface{})
LocalAddr() net.Addr
RemoteAddr() net.Addr
Close()
Destroy()
UserData() interface{}
SetUserData(data interface{})
}
在gate.go里,會(huì)有一個(gè)agent 結(jié)構(gòu)體來實(shí)現(xiàn)Agent接口禁偎。除了Agent接口中的方法腿堤,agent還實(shí)現(xiàn)了Run方法和OnClose方法。
type agent struct {
conn network.Conn
gate *Gate
userData interface{}
}
這個(gè)結(jié)構(gòu)體又引入了一個(gè)Gate如暖,這是啥笆檀?在gate.go里也能找到:
type Gate struct {
MaxConnNum int
PendingWriteNum int
MaxMsgLen uint32
Processor network.Processor
AgentChanRPC *chanrpc.Server
// websocket
WSAddr string
HTTPTimeout time.Duration
CertFile string
KeyFile string
// tcp
TCPAddr string
LenMsgLen int
LittleEndian bool
}
看起來有一些配置參數(shù),還有一個(gè)數(shù)據(jù)解析器Processor盒至,和AgentChanRPC *chanrpc.Server酗洒,看一下怎么用的吧。
Gate只有兩個(gè)方法枷遂,OnDestroy目前是空的樱衷,還有一個(gè)是Run,不出意外的話酒唉,應(yīng)該是解析那些配置參數(shù)矩桂,啟動(dòng)服務(wù):
func (gate *Gate) Run(closeSig chan bool) {
var wsServer *network.WSServer
if gate.WSAddr != "" {
wsServer = new(network.WSServer)
wsServer.Addr = gate.WSAddr
wsServer.MaxConnNum = gate.MaxConnNum
wsServer.PendingWriteNum = gate.PendingWriteNum
wsServer.MaxMsgLen = gate.MaxMsgLen
wsServer.HTTPTimeout = gate.HTTPTimeout
wsServer.CertFile = gate.CertFile
wsServer.KeyFile = gate.KeyFile
wsServer.NewAgent = func(conn *network.WSConn) network.Agent {
a := &agent{conn: conn, gate: gate}
if gate.AgentChanRPC != nil {
gate.AgentChanRPC.Go("NewAgent", a)
}
return a
}
}
var tcpServer *network.TCPServer
if gate.TCPAddr != "" {
tcpServer = new(network.TCPServer)
tcpServer.Addr = gate.TCPAddr
tcpServer.MaxConnNum = gate.MaxConnNum
tcpServer.PendingWriteNum = gate.PendingWriteNum
tcpServer.LenMsgLen = gate.LenMsgLen
tcpServer.MaxMsgLen = gate.MaxMsgLen
tcpServer.LittleEndian = gate.LittleEndian
tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent {
a := &agent{conn: conn, gate: gate}
if gate.AgentChanRPC != nil {
gate.AgentChanRPC.Go("NewAgent", a)
}
return a
}
}
if wsServer != nil {
wsServer.Start()
}
if tcpServer != nil {
tcpServer.Start()
}
<-closeSig
if wsServer != nil {
wsServer.Close()
}
if tcpServer != nil {
tcpServer.Close()
}
}
這里啟動(dòng)了兩種不同類型的Server,closeSig那個(gè)暫時(shí)忽略不說。Server使用NewAgent回調(diào)黔州,把gate傳走了耍鬓,呃,有點(diǎn)懵逼流妻。還是回到官方例子中看看整個(gè)使用流程吧
二牲蜀、 官方例子LeafServer中的Module
1.Module初始化
首先在main.go中
leaf.Run(
game.Module,
gate.Module,
login.Module,
)
這里gate.Module實(shí)際上是由gate包里的external.go暴露出來的(這也是leaf的使用習(xí)慣,所有module都這樣暴露)绅这。
//src/server/gate/external.go
type Module struct {
*gate.Gate
}
func (m *Module) OnInit() {
m.Gate = &gate.Gate{
MaxConnNum: conf.Server.MaxConnNum,
PendingWriteNum: conf.PendingWriteNum,
MaxMsgLen: conf.MaxMsgLen,
WSAddr: conf.Server.WSAddr,
HTTPTimeout: conf.HTTPTimeout,
CertFile: conf.Server.CertFile,
KeyFile: conf.Server.KeyFile,
TCPAddr: conf.Server.TCPAddr,
LenMsgLen: conf.LenMsgLen,
LittleEndian: conf.LittleEndian,
Processor: msg.Processor,
AgentChanRPC: game.ChanRPC,
}
}
匿名結(jié)構(gòu)體Gate了涣达,又額外實(shí)現(xiàn)一個(gè)OnInit方法,感覺像是有一個(gè)IModule這樣的接口呢,找一找:
在源碼的module.go中度苔,確實(shí)找到了:
type Module interface {
OnInit()
OnDestroy()
Run(closeSig chan bool)
}
結(jié)合上面第一部分說的Gate實(shí)現(xiàn)了OnDestroy和Run方法匆篓,官方例子中的gate/external.go確是實(shí)現(xiàn)了Module接口。注意其OnInit中寇窑,除了一堆屬性從conf配置中讀取鸦概,還引入了msg.Processor,這明顯是個(gè)網(wǎng)絡(luò)消息解析器甩骏。然后game.ChanRPC窗市,這看起來是轉(zhuǎn)到game模塊去了,所以在一開始main.go中的leaf.Run中饮笛,也是先傳入的game.Module咨察,然后才是gate.Module。
//leaf.go
func Run(mods ...module.Module) {
...
log.Release("Leaf %v starting up", version)
// module
for i := 0; i < len(mods); i++ {
module.Register(mods[i])
}
module.Init()
...
2.module是怎么運(yùn)行起來的
再次回到源碼module.go福青,節(jié)選一部分代碼過來
type module struct {
mi Module
closeSig chan bool
wg sync.WaitGroup
}
var mods []*module
func Register(mi Module) {
m := new(module)
m.mi = mi
m.closeSig = make(chan bool, 1)
mods = append(mods, m)
}
func Init() {
for i := 0; i < len(mods); i++ {
mods[i].mi.OnInit()
}
for i := 0; i < len(mods); i++ {
m := mods[i]
m.wg.Add(1)
go run(m)
}
}
func run(m *module) {
m.mi.Run(m.closeSig)
m.wg.Done()
}
看到這些摄狱,是不是想起來官方文檔說的這段話:
Leaf 首先會(huì)在同一個(gè) goroutine 中按模塊注冊順序執(zhí)行模塊的 OnInit 方法,等到所有模塊 OnInit 方法執(zhí)行完成后則為每一個(gè)模塊啟動(dòng)一個(gè) goroutine 并執(zhí)行模塊的 Run 方法无午。最后媒役,游戲服務(wù)器關(guān)閉時(shí)(Ctrl + C 關(guān)閉游戲服務(wù)器)將按模塊注冊相反順序在同一個(gè) goroutine 中執(zhí)行模塊的 OnDestroy 方法。
三宪迟、綜述
1.流程
現(xiàn)在來理一理思路刊愚。從main.go里開始,leaf.Run注冊并運(yùn)行了game,gate,login三個(gè)module踩验。重點(diǎn)關(guān)注gate這個(gè)module鸥诽,這個(gè)module通過組合方式實(shí)現(xiàn)了Module接口,即自己項(xiàng)目里實(shí)現(xiàn)OnInit方法箕憾,通過匿名結(jié)構(gòu)體gate.Gate在源碼里實(shí)現(xiàn)OnDestroy和Run方法牡借。其中,OnInit方法里把gate.Gate制造出來了袭异,部分屬性讀取conf的配置钠龙,Processor指定成自己項(xiàng)目的消息解析,AgentChanRPC指定了自己項(xiàng)目里的game模塊御铃。
...
Processor: msg.Processor,
AgentChanRPC: game.ChanRPC,
...
然后按照流程繼續(xù)走碴里,gate模塊的OnInit執(zhí)行完,就要去執(zhí)行Run了上真。這個(gè)方法在本文第一部分就看過了咬腋,當(dāng)時(shí)卡在一個(gè)懵逼的地方:
tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent {
a := &agent{conn: conn, gate: gate}
if gate.AgentChanRPC != nil {
gate.AgentChanRPC.Go("NewAgent", a)
}
return a
}
現(xiàn)在有點(diǎn)感覺了吧,也就是說tcpServer執(zhí)行NewAgent時(shí)睡互,看單詞名字意思是一個(gè)新連接事件發(fā)生時(shí)根竿,實(shí)際會(huì)轉(zhuǎn)交給gate.AgentChanRPC去執(zhí)行陵像,也就是例子中的game.ChanRPC。轉(zhuǎn)交方式是.Go("NewAgent", a)
寇壳,就像拋出一個(gè)事件一樣醒颖,有一個(gè)名稱,有一個(gè)參數(shù)壳炎∨⑶福可以去game模塊的chanrpc.go驗(yàn)證一下
//game.internal.chanrpc.go
func init() {
skeleton.RegisterChanRPC("NewAgent", rpcNewAgent)
skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent)
}
func rpcNewAgent(args []interface{}) {
a := args[0].(gate.Agent)
_ = a
}
func rpcCloseAgent(args []interface{}) {
a := args[0].(gate.Agent)
_ = a
}
2.tcpServer什么時(shí)候執(zhí)行NewAgent
在gate的Run方法中,提到了tcpServer會(huì)根據(jù)參數(shù)生成并運(yùn)行
...
if tcpServer != nil {
tcpServer.Start()
}
...
然后去tcp_server.go看一下
func (server *TCPServer) Start() {
server.init()
go server.run()
}
init和run細(xì)節(jié)有點(diǎn)多匿辩,先忽略掉吧疏日。我們是來找NewAgent的,終于在run中找到了:
...
tcpConn := newTCPConn(conn, server.PendingWriteNum, server.msgParser)
agent := server.NewAgent(tcpConn)
go func() {
agent.Run()
// cleanup
tcpConn.Close()
server.mutexConns.Lock()
delete(server.conns, conn)
server.mutexConns.Unlock()
agent.OnClose()
server.wgConns.Done()
}()
首先這段代碼是在一個(gè)for循環(huán)中的撒汉,也就是收到tcp消息時(shí),才會(huì)執(zhí)行涕滋。具體基礎(chǔ)知識(shí)參考Golang socket websocket睬辐。agent在拿到具體的tcpConn,會(huì)執(zhí)行自己的Run方法宾肺,回到源碼gate.go的agent結(jié)構(gòu)體可以看到:
type agent struct {
conn network.Conn
gate *Gate
userData interface{}
}
func (a *agent) Run() {
for {
data, err := a.conn.ReadMsg()
if err != nil {
log.Debug("read message: %v", err)
break
}
if a.gate.Processor != nil {
msg, err := a.gate.Processor.Unmarshal(data)
...
}
開始使用相應(yīng)的Processor去讀取數(shù)據(jù)了溯饵。
本篇暫時(shí)先到這里,還有許多細(xì)節(jié)锨用,留待后續(xù)系列慢慢深究丰刊。