From 77f5eec7ee525597a19cda7029bf4a512f3ac8c0 Mon Sep 17 00:00:00 2001 From: zqj <582132116@qq.com> Date: Fri, 18 Mar 2022 11:33:30 +0800 Subject: [PATCH] plugin 插件热更 接口 --- Makefile | 10 +++++----- cmd/gameserver/agent.go | 34 +++++++++++++++++++--------------- cmd/gameserver/game.go | 24 +++++++++++++++++------- cmd/gameserver/plugin/RolePlugin.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/gameserver/plugin/protocode.go | 16 ++++++++++++++++ cmd/httpserver/http.go | 9 ++++++++- common/components/conn.go | 85 ++++++++++++++++++++++++++++++++++++++++++------------------------------------------- common/components/icompontents.go | 8 ++++++++ common/components/plugin.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ common/components/server.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- plugin/RolePlugin.go | 58 ---------------------------------------------------------- plugin/protocode.go | 17 ----------------- tools/protostostruct.go | 6 +++--- 13 files changed, 291 insertions(+), 176 deletions(-) create mode 100644 cmd/gameserver/plugin/RolePlugin.go create mode 100644 cmd/gameserver/plugin/protocode.go create mode 100644 common/components/plugin.go delete mode 100644 plugin/RolePlugin.go delete mode 100644 plugin/protocode.go diff --git a/Makefile b/Makefile index bb091e1..b7db157 100644 --- a/Makefile +++ b/Makefile @@ -11,18 +11,18 @@ test: http: go run -race cmd/httpserver/http.go cmd/httpserver/AccountAction.go -game: +game:plugin go run -race cmd/gameserver/*.go build: go build -race -o bin/account cmd/http.go go build -race -o bin/game cmd/gameserver/*.go go build -race -o bin/test cmd/test/client.go regame:plugin - lsof -i:8849 | grep "game" | grep -v grep | awk '{print $$2}' | xargs -I {} kill -USR1 {} + lsof -i:8850 | grep "agent" | grep -v grep | awk '{print $$2}' | xargs -I {} kill -USR1 {} plugin: - #go build -ldflags -pluginpath="plugin/hot-1" --buildmode=plugin -o bin/plugin.so src/plugin/*.go - go build --buildmode=plugin -o bin/$(pname) src/plugin/*.go - cd bin && rm -rf plugin.so && ln -s $(pname) plugin.so && cd - + cd bin && rm -rf ./plugin*.so && cd - + go build -race --buildmode=plugin -o bin/$(pname) cmd/gameserver/plugin/*.go + cd bin && ln -s $(pname) plugin.so && cd - .PHONY: all build protos test cert plugin \ No newline at end of file diff --git a/cmd/gameserver/agent.go b/cmd/gameserver/agent.go index 919be39..60663d3 100644 --- a/cmd/gameserver/agent.go +++ b/cmd/gameserver/agent.go @@ -1,13 +1,11 @@ package main import ( - "fmt" "github.com/golang/protobuf/proto" "math" "pro2d/common" "pro2d/common/components" "pro2d/models" - "pro2d/pb" "pro2d/utils" "pro2d/utils/logger" "sync/atomic" @@ -45,24 +43,30 @@ func (c *Agent) OnConnection(conn components.IConnection) { func (c *Agent) OnMessage(msg components.IMessage) { atomic.StoreInt64(&c.lastHeartCheckTime, utils.Timex()) - if md, ok := components.ActionMap[pb.ProtoCode(msg.GetHeader().GetMsgID())]; ok { - logger.Debug("protocode handler: %d", msg.GetHeader().GetMsgID()) - errCode, protomsg := md(msg) - rsp, err := proto.Marshal(protomsg) - fmt.Printf("errCode: %d, protomsg:%v\n", errCode, protomsg) - if err != nil { - conn := c.Server.GetIConnection(msg.GetSessId()) - if conn != nil { - conn.Send(-100, msg.GetHeader().GetMsgID(), nil) - } - return - } + md := c.Server.GetPlugin().GetAction(msg.GetHeader().GetMsgID()) + if md == nil { + logger.Debug("cmd: %d, md is nil", msg.GetHeader().GetMsgID()) + return + } + + logger.Debug("protocode handler: %d", msg.GetHeader().GetMsgID()) + //fmt.Printf("errCode: %d, protomsg:%v\n", errCode, protomsg) + + f := md.(func (conn components.IConnection, msg components.IMessage) (int32, interface{})) + errCode, protomsg := f(c, msg) + rsp, err := proto.Marshal(protomsg.(proto.Message)) + if err != nil { conn := c.Server.GetIConnection(msg.GetSessId()) if conn != nil { - conn.Send(errCode, msg.GetHeader().GetMsgID(), rsp) + conn.Send(-100, msg.GetHeader().GetMsgID(), nil) } return } + conn := c.Server.GetIConnection(msg.GetSessId()) + if conn != nil { + conn.Send(errCode, msg.GetHeader().GetMsgID(), rsp) + } + return logger.Error("protocode not handler: %d", msg.GetHeader().GetMsgID()) } diff --git a/cmd/gameserver/game.go b/cmd/gameserver/game.go index 5d26a48..6c8ce02 100644 --- a/cmd/gameserver/game.go +++ b/cmd/gameserver/game.go @@ -10,7 +10,7 @@ import ( "pro2d/common/components" "pro2d/conf" "pro2d/models" - _ "pro2d/plugin" + //_ "pro2d/cmd/gameserver/plugin" "pro2d/utils/db" "pro2d/utils/etcd" "pro2d/utils/logger" @@ -27,13 +27,20 @@ type GameServer struct { func NewGameServer(sconf *conf.SConf) (*GameServer, error) { s := &GameServer{ - IServer: components.NewServer(sconf.Port, conf.GlobalConf.GameConf.PluginPath, components.NewPBSplitter()), Agents: new(sync.Map), } - s.SetConnectionCallback(s.OnConnection) - s.SetMessageCallback(s.OnMessage) - s.SetCloseCallback(s.OnClose) - s.SetTimerCallback(s.OnTimer) + + options := []components.Option{ + components.WithPlugin(components.NewPlugin(sconf.PluginPath)), + components.WithSplitter(components.NewPBSplitter()), + components.WithConnCbk(s.OnConnection), + components.WithMsgCbk(s.OnMessage), + components.WithCloseCbk(s.OnClose), + components.WithTimerCbk(s.OnTimer), + } + + iserver := components.NewServer(sconf.Port, options...) + s.IServer = iserver //mongo 初始化 db.MongoDatabase = db.MongoClient.Database(sconf.DBName) @@ -120,7 +127,10 @@ func main() { return case u := <-userChan: logger.Debug("userChan .. %v",u.String()) - //s.LoadPlugin() + e := s.IServer.GetPlugin().LoadPlugin() + if e != nil { + logger.Error("err: ", e.Error()) + } } } } diff --git a/cmd/gameserver/plugin/RolePlugin.go b/cmd/gameserver/plugin/RolePlugin.go new file mode 100644 index 0000000..8303dce --- /dev/null +++ b/cmd/gameserver/plugin/RolePlugin.go @@ -0,0 +1,57 @@ +package main + +import ( + "github.com/golang/protobuf/proto" + "pro2d/common/components" + "pro2d/conf" + "pro2d/models" + "pro2d/pb" + "pro2d/utils/logger" +) + +func HeartRpc(msg components.IMessage) (int32, interface{}) { + //msg.Conn.SetLastHeartCheckTime() + return 0, nil +} + +func CreateRpc(msg components.IMessage) (int32, interface{}) { + req := pb.CreateReq{} + if err := proto.Unmarshal(msg.GetData(), &req); err != nil { + logger.Error("CreateRpc err: %v", err) + return 1, nil + } + role := models.RoleExistByUid(req.Uid) + if role != nil { + return 2, nil + } + + roleId := conf.SnowFlack.NextValStr() + role = models.NewRole(roleId) + if err := role.Create(); err != nil { + logger.Error("CreateRpc role create err: %v", err) + return 3, nil + } + return 0, nil +} + +func LoginRpc(msg components.IMessage) (int32, interface{}) { + //logger.Debug("11111111cmd: %v, msg: %s", msg.GetHeader().GetMsgID(), msg.GetData()) + req := pb.LoginReq{} + if err := proto.Unmarshal(msg.GetData(), &req); err != nil { + logger.Error("loginRpc err: %v", err) + return 1, nil + } + + role := models.RoleExistByUid(req.Uid) + if role == nil { + return 2, nil + } + role.SetProperty("Device", req.Device) + + return 0, &pb.RoleRsp{ + Role: role.Role, + Hero: role.GetAllHero(), + Team: role.Teams.Team, + Equips: []*pb.Equipment{role.Equip.Equip}, + } +} \ No newline at end of file diff --git a/cmd/gameserver/plugin/protocode.go b/cmd/gameserver/plugin/protocode.go new file mode 100644 index 0000000..8ca8e1e --- /dev/null +++ b/cmd/gameserver/plugin/protocode.go @@ -0,0 +1,16 @@ +package main + +import ( + "pro2d/pb" + "pro2d/utils/logger" +) + +func GetActionMap() map[interface{}]interface{} { + logger.Debug("init protocode...") + am := make(map[interface{}]interface{}) + am[pb.ProtoCode_HeartReq] = HeartRpc + am[pb.ProtoCode_LoginReq] = LoginRpc + am[pb.ProtoCode_CreateReq] = CreateRpc + + return am +} \ No newline at end of file diff --git a/cmd/httpserver/http.go b/cmd/httpserver/http.go index 8db21bd..37a0041 100644 --- a/cmd/httpserver/http.go +++ b/cmd/httpserver/http.go @@ -23,7 +23,7 @@ func NewAccountServer(version string, port ...string) *AccountServer { return &AccountServer{IHttp: components.NewHttpServer(version, port...)} } -func (s *AccountServer) Start() error { +func (s *AccountServer) Init() error { //mongo 初始化 db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.AccountConf.DBName) models.InitAccountServerModels() @@ -35,6 +35,13 @@ func (s *AccountServer) Start() error { return err } s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.AccountConf.Name, conf.GlobalConf.AccountConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.AccountConf.IP, conf.GlobalConf.AccountConf.Port), 5) + return nil +} + +func (s *AccountServer) Start() error { + if err := s.Init(); err != nil { + return err + } return s.IHttp.Start() } diff --git a/common/components/conn.go b/common/components/conn.go index 29a2b75..c12b7c1 100644 --- a/common/components/conn.go +++ b/common/components/conn.go @@ -74,6 +74,47 @@ func (c *Connection) SetTimerCallback(cb TimerCallback) { c.timerCallback = cb } +func (c *Connection) Start() { + go c.write() + go c.read() + go c.listen() + + c.Status = 1 + c.connectionCallback(c) + c.handleTimeOut() +} + +func (c *Connection) Stop() { + logger.Debug("ID: %d close", c.Id) + closed := atomic.LoadUint32(&c.Status) + if closed == 0 { + return + } + atomic.StoreUint32(&c.Status, 0) + + close(c.WBuffer) + close(c.Quit) + c.Conn.Close() + c.closeCallback(c) +} + +func (c *Connection) Send(errCode int32, cmd uint32, data []byte) error{ + buf, err := c.Server.GetSplitter().Pack(cmd, data, errCode, 0) + if err != nil { + return err + } + + sendTimeout := time.NewTimer(5 * time.Millisecond) + defer sendTimeout.Stop() + // 发送超时 + select { + case <-sendTimeout.C: + return fmt.Errorf("send buff msg timeout") + case c.WBuffer <- buf: + return nil + } +} + func (c *Connection) defaultConnectionCallback(conn IConnection) { } @@ -124,6 +165,7 @@ func (c *Connection) read() { } } +//此设计目的是为了让网络数据与定时器处理都在一条协程里处理。不想加锁。。。 func (c *Connection) listen(){ defer c.Stop() @@ -139,7 +181,6 @@ func (c *Connection) listen(){ } } - func (c *Connection) handleTimeOut() { c.timerFunc <- func() { c.timerCallback(c) @@ -150,45 +191,3 @@ func (c *Connection) handleTimeOut() { func (c *Connection) Quitting() { c.Quit <- c } - -func (c *Connection) Start() { - go c.write() - go c.read() - go c.listen() - - c.Status = 1 - c.connectionCallback(c) - c.handleTimeOut() -} - -func (c *Connection) Stop() { - logger.Debug("ID: %d close", c.Id) - closed := atomic.LoadUint32(&c.Status) - if closed == 0 { - return - } - atomic.StoreUint32(&c.Status, 0) - - close(c.WBuffer) - close(c.Quit) - c.Conn.Close() - c.closeCallback(c) -} - -func (c *Connection) Send(errCode int32, cmd uint32, data []byte) error{ - buf, err := c.Server.GetSplitter().Pack(cmd, data, errCode, 0) - if err != nil { - return err - } - - sendTimeout := time.NewTimer(5 * time.Millisecond) - defer sendTimeout.Stop() - // 发送超时 - select { - case <-sendTimeout.C: - return fmt.Errorf("send buff msg timeout") - case c.WBuffer <- buf: - return nil - } -} - diff --git a/common/components/icompontents.go b/common/components/icompontents.go index 2b0d701..c88b381 100644 --- a/common/components/icompontents.go +++ b/common/components/icompontents.go @@ -54,6 +54,7 @@ type IServer interface { GetSplitter() ISplitter GetIConnection(id int) IConnection + GetPlugin() IPlugin SetConnectionCallback(ConnectionCallback) SetMessageCallback(MessageCallback) @@ -66,4 +67,11 @@ type IHttp interface { Start() error Stop() BindHandler(interface{}) +} + +type ActionHandler func (conn IConnection, msg IMessage) (int32, interface{}) +//用于热更逻辑的插件接口 +type IPlugin interface { + LoadPlugin() error + GetAction(uint32) interface{} } \ No newline at end of file diff --git a/common/components/plugin.go b/common/components/plugin.go new file mode 100644 index 0000000..5c3aa1f --- /dev/null +++ b/common/components/plugin.go @@ -0,0 +1,62 @@ +package components + +import ( + "plugin" + "pro2d/pb" + "pro2d/utils/logger" + "sync" +) + +type ActionMap sync.Map//map[pb.ProtoCode]ActionHandler + +type PluginOption func(*Plugin) + +type Plugin struct { + pluginPath string + + Actions sync.Map +} + +func NewPlugin(path string, options ...PluginOption) IPlugin{ + p := &Plugin{ + pluginPath: path, + } + for _, option := range options { + option(p) + } + return p +} + +func (p *Plugin) LoadPlugin() error { + plu, err := plugin.Open(p.pluginPath) + + if err != nil { + return err + } + logger.Debug("func LoadPlugin open success...") + + f, err := plu.Lookup("GetActionMap") + if err != nil { + return err + } + logger.Debug("func LoadPlugin Lookup success...") + + if x, ok := f.(func()map[interface{}]interface{}); ok { + logger.Debug("func LoadPlugin GetActionMap success...") + am := x() + for k, v := range am { + p.Actions.Delete(k) + p.Actions.Store(k, v) + } + } + + return nil +} + +func (p *Plugin) GetAction(cmd uint32) interface{} { + f, ok := p.Actions.Load(pb.ProtoCode(cmd)) + if !ok { + return nil + } + return f +} diff --git a/common/components/server.go b/common/components/server.go index 033cd8d..ed8279a 100644 --- a/common/components/server.go +++ b/common/components/server.go @@ -2,39 +2,74 @@ package components import ( "fmt" - "github.com/golang/protobuf/proto" "net" - "plugin" - "pro2d/pb" "pro2d/utils/logger" "sync" ) -type ActionHandler func (msg IMessage) (int32, proto.Message) -var ActionMap map[pb.ProtoCode]ActionHandler +type Option func(*Server) + +func WithPlugin(iPlugin IPlugin) Option { + return func(server *Server) { + server.plugins = iPlugin + } +} + +func WithSplitter(splitter ISplitter) Option { + return func(server *Server) { + server.splitter = splitter + } +} + +func WithConnCbk(cb ConnectionCallback) Option { + return func(server *Server) { + server.connectionCallback = cb + } +} + +func WithMsgCbk(cb MessageCallback) Option { + return func(server *Server) { + server.messageCallback = cb + } +} + +func WithCloseCbk(cb CloseCallback) Option { + return func(server *Server) { + server.closeCallback = cb + } +} + +func WithTimerCbk(cb TimerCallback) Option { + return func(server *Server) { + server.timerCallback = cb + } +} + type Server struct { - IServer + PluginPath string + plugins IPlugin + splitter ISplitter + actionHandlers sync.Map connectionCallback ConnectionCallback messageCallback MessageCallback closeCallback CloseCallback timerCallback TimerCallback - splitter ISplitter - port int - PluginPath string Clients *sync.Map } -func NewServer(port int, pluginPath string, splitter ISplitter) *Server { +func NewServer(port int, options ...Option) IServer { s := &Server{ - splitter: splitter, port: port, - PluginPath: pluginPath, Clients: new(sync.Map), } + for _, option := range options { + option(s) + } + return s } @@ -50,6 +85,10 @@ func (s *Server) GetIConnection(id int) IConnection { return c.(IConnection) } +func (s *Server) GetPlugin() IPlugin { + return s.plugins +} + func (s *Server) SetConnectionCallback(cb ConnectionCallback) { s.connectionCallback = cb } @@ -82,23 +121,11 @@ func (s *Server) removeConnection(conn IConnection) { s.Clients.Delete(conn.GetID()) } -func (s *Server) LoadPlugin() { - //重新加载 - _, err:=plugin.Open(s.PluginPath) - if err != nil { - logger.Error("load plugin err: %v, %s", err, s.PluginPath) - return +func (s *Server) Start() error { + if err := s.plugins.LoadPlugin(); err != nil { + return err } - logger.Debug("load plugin success") -} - -func (s *Server) Start() error { - //初始化plugin - //_, err = plugin.Open(conf.GlobalConf.GameConf.PluginPath) - //if err != nil { - // return err - //} port := fmt.Sprintf(":%d", s.port) l, err := net.Listen("tcp", port) if err != nil { diff --git a/plugin/RolePlugin.go b/plugin/RolePlugin.go deleted file mode 100644 index 3b5004e..0000000 --- a/plugin/RolePlugin.go +++ /dev/null @@ -1,58 +0,0 @@ -package plugin - -import ( - "github.com/golang/protobuf/proto" - "pro2d/common/components" - "pro2d/conf" - "pro2d/models" - "pro2d/pb" - "pro2d/utils/logger" -) - -func HeartRpc(msg components.IMessage) (int32, proto.Message) { - //msg.Conn.SetLastHeartCheckTime() - return 0, nil -} - -func CreateRpc(msg components.IMessage) (int32, proto.Message) { - req := pb.CreateReq{} - if err := proto.Unmarshal(msg.GetData(), &req); err != nil { - logger.Error("CreateRpc err: %v", err) - return 1, nil - } - role := models.RoleExistByUid(req.Uid) - if role != nil { - return 2, nil - } - - roleId := conf.SnowFlack.NextValStr() - role = models.NewRole(roleId) - if err := role.Create(); err != nil { - logger.Error("CreateRpc role create err: %v", err) - return 3, nil - } - return 0, nil -} - -func LoginRpc(msg components.IMessage) (int32, proto.Message) { - //logger.Debug("cmd: %v, msg: %s", msg.PBHead.Cmd, msg.Body) - req := pb.LoginReq{} - if err := proto.Unmarshal(msg.GetData(), &req); err != nil { - logger.Error("loginRpc err: %v", err) - return 1, nil - } - - role := models.RoleExistByUid(req.Uid) - if role == nil { - return 2, nil - } - role.SetProperty("Device", req.Device) - - - return 0, &pb.RoleRsp{ - Role: role.Role, - Hero: role.GetAllHero(), - Team: role.Teams.Team, - Equips: []*pb.Equipment{role.Equip.Equip}, - } -} \ No newline at end of file diff --git a/plugin/protocode.go b/plugin/protocode.go deleted file mode 100644 index dc1410b..0000000 --- a/plugin/protocode.go +++ /dev/null @@ -1,17 +0,0 @@ -package plugin - -import ( - "pro2d/common/components" - "pro2d/pb" - "pro2d/utils/logger" -) - -func init() { - logger.Debug("init protocode...") - components.ActionMap = make(map[pb.ProtoCode]components.ActionHandler) - - components.ActionMap[pb.ProtoCode_HeartReq] = HeartRpc - components.ActionMap[pb.ProtoCode_LoginReq] = LoginRpc - components.ActionMap[pb.ProtoCode_CreateReq] = CreateRpc - -} diff --git a/tools/protostostruct.go b/tools/protostostruct.go index 9bfa6af..2e3a01e 100644 --- a/tools/protostostruct.go +++ b/tools/protostostruct.go @@ -15,8 +15,8 @@ var ( ProtoCodeLineReq = "\t%sReq = %d;\n" ProtoCodeLineRsp = "\t%sRsp = %d;\n" - GoProtoCodeStr = "package main\n\nimport (\n\t\"pro2d/pb\"\n\t\"pro2d/src/components/logger\"\n\t\"pro2d/src/components/net\"\n)\n\nfunc init() {\n\tlogger.Debug(\"init protocode...\")\n\tnet.ActionMap = make(map[pb.ProtoCode]net.ActionHandler)\n\n%s\n}\n" - GoProtoCodeLine = "\tnet.ActionMap[pb.ProtoCode_%sReq] = %sRpc\n" + GoProtoCodeStr = "package main\n\nimport (\n\t\"pro2d/pb\"\n\t\"pro2d/utils/logger\"\n)\n\nfunc GetActionMap() map[interface{}]interface{} {\n\tlogger.Debug(\"init protocode...\")\n\tam := make(map[interface{}]interface{})\n%s\n\treturn am\n}" + GoProtoCodeLine = "\tam[pb.ProtoCode_%sReq] = %sRpc\n" ) func ProtoToCode(readPath, filename string) (string, string) { @@ -106,7 +106,7 @@ func ReadProtos(readPath, OutPath string ) error { return fmt.Errorf("WriteNewFile|Write is err:%v", err) } - fw, err = os.OpenFile( OutPath+"src/plugin/protocode.go", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + fw, err = os.OpenFile( OutPath+"cmd/gameserver/plugin/protocode.go", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return fmt.Errorf("WriteNewFile|OpenFile is err:%v", err) } -- libgit2 0.21.2