五邑隱俠,本名關健昌,12年遊戲生涯。 本教程以Go語言為例。
RPC指遠端方法呼叫,遊戲裡引入RPC目的是降低跨程式互動的複雜度。
遊戲業務設計為多go routine,一個玩家一個go routine。遊戲裡RPC客戶端阻塞式呼叫遠端(服務程式)方法,這樣處理的好處是跨程式互動的業務也可以按照單執行緒順序執行的思路實現。
RPC請求包由以下幾部分組成:標記(字串,用於區分是哪類呼叫)、序列號(一次呼叫的唯一標記)、方法編號(用於對映呼叫的方法)、引數。
RPC響應包由以下幾部分組成:標記、序列號、方法編號、返回值。
type RpcRequest struct { Mark []byte FuncNo uint16 SerialNo uint16 Params []byte } type RpcResponse struct { Mark []byte FuncNo uint16 SerialNo uint16 ReturnVal []byte }
RPC建立在P2P網路之上,在 P2pListener interface 的 OnP2pCall(p2p *P2pNet, pack *P2pPack) 實現方法,根據包頭前幾個位元組與 Mark 對比,如果一致則呼叫對應的RPC客戶端/服務端進行處理。
對於RPC服務,可以參考http服務,建立方法編號到處理方法的對映。
type RpcHandler func(params []byte) ([]byte, error) type RpcServ struct { p2p *P2pNet mark []byte mapFuncNo2Handler map[uint16]RpcHandler } func (s *RpcServ) HandleFunc(funcNo uint16, handler func(params []byte) ([]byte, error)) { if handler == nil { return }
s.mapFuncNo2Handler[funcNo] = handler }
當收到請求後,解析出funcNo和引數Params,呼叫對應的處理器進行處理。
handler, ok := s.mapFuncNo2Handler[rpc.FuncNo] if !ok { return fmt.Errorf("no handler for funcNo %d", rpc.FuncNo) }
returnVal, err := handler(rpc.Params)
RPC服務對請求的處理可以是非阻塞的。
一般只對RPC客戶端做成阻塞的,這樣在客戶端呼叫遠端方法時,客戶端會一直等待服務端返回才繼續往下執行,整個邏輯流程跟單執行緒執行一樣。
通過序列號SerialNo、方法號 FuncNo 雙重確認響應包對應於當前請求。
在go語言裡,可以通過定義一個長度為1的 chan byte,請求發出後,讀取通道,如果還沒有收到響應就會阻塞,響應返回時往通道里寫個1,喚醒呼叫流程繼續執行。
func Wait(e chan byte) { <-e } func Signal(e chan byte) { e <- 1 }
呼叫方法的引數和返回值都是二進位制,業務可以根據自己的需要用json或 protobuff 進行序列化/反序列化。
func (c *RpcClient) invoke(params []byte) ([]byte, error)
RPC呼叫介紹到這裡。接下來介紹下游戲業務程式的實現。