五邑隱俠,本名關健昌,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調用介紹到這里。接下來介紹下游戲業務進程的實現。