From e96e9e3d3c8efb429447f1ec295b5fe956307568 Mon Sep 17 00:00:00 2001 From: flswld Date: Sun, 25 Dec 2022 00:42:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=BD=91=E5=85=B3=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E4=B8=8D=E5=90=8C=E7=89=88=E6=9C=AC=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=8D=8F=E8=AE=AE=E4=BB=A3=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + cmd/gate/application.toml | 1 + common/config/config.go | 11 ++-- gate/client_proto/client_proto.go | 58 +++++++++++++++++++++ gate/client_proto/client_proto_gen_test.go | 33 ++++++++++++ gate/net/kcp_connect_manager.go | 32 +++++++----- gate/net/proto_endecode.go | 60 +++++++++++++++++++++- pkg/object/object.go | 52 ++++++++++++++++++- pkg/reflection/struct.go | 58 ++++++++++++++++++++- pkg/reflection/struct_test.go | 49 ++++++++++++++++++ protocol/cmd/cmd_id_proto_obj_map.go | 54 ++++++++++++++----- 11 files changed, 374 insertions(+), 36 deletions(-) create mode 100644 gate/client_proto/client_proto.go create mode 100644 gate/client_proto/client_proto_gen_test.go create mode 100644 pkg/reflection/struct_test.go diff --git a/.gitignore b/.gitignore index 32e557e8..a8132f35 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ bin # Game protocol protobuf generate file protocol/proto/*.pb.go +gate/client_proto/proto +gate/client_proto/client_proto_gen.go # Log file *.log diff --git a/cmd/gate/application.toml b/cmd/gate/application.toml index f19adaf2..0bd44456 100644 --- a/cmd/gate/application.toml +++ b/cmd/gate/application.toml @@ -1,6 +1,7 @@ [hk4e] kcp_addr = "127.0.0.1" kcp_port = 22103 +client_proto_proxy_enable = false [logger] level = "DEBUG" diff --git a/common/config/config.go b/common/config/config.go index 906996bf..021a6323 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -32,11 +32,12 @@ type Database struct { // Hk4e 原神相关 type Hk4e struct { - KcpPort int32 `toml:"kcp_port"` - KcpAddr string `toml:"kcp_addr"` - ResourcePath string `toml:"resource_path"` - GameDataConfigPath string `toml:"game_data_config_path"` - GachaHistoryServer string `toml:"gacha_history_server"` + KcpPort int32 `toml:"kcp_port"` + KcpAddr string `toml:"kcp_addr"` + ResourcePath string `toml:"resource_path"` + GameDataConfigPath string `toml:"game_data_config_path"` + GachaHistoryServer string `toml:"gacha_history_server"` + ClientProtoProxyEnable bool `toml:"client_proto_proxy_enable"` } // MQ 消息队列 diff --git a/gate/client_proto/client_proto.go b/gate/client_proto/client_proto.go new file mode 100644 index 00000000..89d083be --- /dev/null +++ b/gate/client_proto/client_proto.go @@ -0,0 +1,58 @@ +package client_proto + +import ( + "os" + "strconv" + "strings" + + "hk4e/pkg/logger" +) + +type ClientCmdProtoMap struct { + clientCmdIdCmdNameMap map[uint16]string + clientCmdNameCmdIdMap map[string]uint16 +} + +func NewClientCmdProtoMap() (r *ClientCmdProtoMap) { + r = new(ClientCmdProtoMap) + r.clientCmdIdCmdNameMap = make(map[uint16]string) + r.clientCmdNameCmdIdMap = make(map[string]uint16) + clientCmdFile, err := os.ReadFile("./client_cmd.csv") + if err != nil { + panic(err) + } + clientCmdData := string(clientCmdFile) + lineList := strings.Split(clientCmdData, "\n") + for _, line := range lineList { + item := strings.Split(line, ",") + if len(item) != 2 { + panic("parse client cmd file error") + } + cmdName := item[0] + cmdId, err := strconv.Atoi(item[1]) + if err != nil { + panic(err) + } + r.clientCmdIdCmdNameMap[uint16(cmdId)] = cmdName + r.clientCmdNameCmdIdMap[cmdName] = uint16(cmdId) + } + return r +} + +func (c *ClientCmdProtoMap) GetClientCmdNameByCmdId(cmdId uint16) string { + cmdName, exist := c.clientCmdIdCmdNameMap[cmdId] + if !exist { + logger.Error("unknown cmd id: %v", cmdId) + return "" + } + return cmdName +} + +func (c *ClientCmdProtoMap) GetClientCmdIdByCmdName(cmdName string) uint16 { + cmdId, exist := c.clientCmdNameCmdIdMap[cmdName] + if !exist { + logger.Error("unknown cmd name: %v", cmdName) + return 0 + } + return cmdId +} diff --git a/gate/client_proto/client_proto_gen_test.go b/gate/client_proto/client_proto_gen_test.go new file mode 100644 index 00000000..9bef96bb --- /dev/null +++ b/gate/client_proto/client_proto_gen_test.go @@ -0,0 +1,33 @@ +package client_proto + +import ( + "os" + "testing" +) + +func TestClientProtoGen(t *testing.T) { + clientCmdProtoMap := NewClientCmdProtoMap() + + fileData := "package client_proto\n" + fileData += "\n" + fileData += "import (\n" + fileData += "\"hk4e/gate/client_proto/proto\"\n" + fileData += "pb \"google.golang.org/protobuf/proto\"\n" + fileData += ")\n" + fileData += "\n" + fileData += "func (c *ClientCmdProtoMap) GetClientProtoObjByCmdName(cmdName string) pb.Message {\n" + fileData += "switch cmdName {\n" + for cmdName := range clientCmdProtoMap.clientCmdNameCmdIdMap { + fileData += "case \"" + cmdName + "\":\nreturn new(proto." + cmdName + ")\n" + } + fileData += "default:\n" + fileData += "return nil\n" + fileData += "}\n" + fileData += "}\n" + fileData += "\n" + + err := os.WriteFile("../client_proto_gen.go", []byte(fileData), 0644) + if err != nil { + panic(err) + } +} diff --git a/gate/net/kcp_connect_manager.go b/gate/net/kcp_connect_manager.go index 5d6a9216..8ea2ecfa 100644 --- a/gate/net/kcp_connect_manager.go +++ b/gate/net/kcp_connect_manager.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "reflect" "strconv" "sync" "time" @@ -12,6 +13,7 @@ import ( "hk4e/common/mq" "hk4e/common/region" "hk4e/common/rpc" + "hk4e/gate/client_proto" "hk4e/gate/kcp" "hk4e/node/api" "hk4e/pkg/logger" @@ -22,18 +24,20 @@ import ( const PacketFreqLimit = 1000 type KcpConnectManager struct { - discovery *rpc.DiscoveryClient - openState bool - sessionConvIdMap map[uint64]*Session - sessionUserIdMap map[uint32]*Session - sessionMapLock sync.RWMutex - kcpEventInput chan *KcpEvent - kcpEventOutput chan *KcpEvent - cmdProtoMap *cmd.CmdProtoMap - messageQueue *mq.MessageQueue - localMsgOutput chan *ProtoMsg - createSessionChan chan *Session - destroySessionChan chan *Session + discovery *rpc.DiscoveryClient + openState bool + sessionConvIdMap map[uint64]*Session + sessionUserIdMap map[uint32]*Session + sessionMapLock sync.RWMutex + kcpEventInput chan *KcpEvent + kcpEventOutput chan *KcpEvent + serverCmdProtoMap *cmd.CmdProtoMap + clientCmdProtoMap *client_proto.ClientCmdProtoMap + clientCmdProtoMapRefValue reflect.Value + messageQueue *mq.MessageQueue + localMsgOutput chan *ProtoMsg + createSessionChan chan *Session + destroySessionChan chan *Session // 密钥相关 dispatchKey []byte signRsaKey []byte @@ -48,7 +52,9 @@ func NewKcpConnectManager(messageQueue *mq.MessageQueue, discovery *rpc.Discover r.sessionUserIdMap = make(map[uint32]*Session) r.kcpEventInput = make(chan *KcpEvent, 1000) r.kcpEventOutput = make(chan *KcpEvent, 1000) - r.cmdProtoMap = cmd.NewCmdProtoMap() + r.serverCmdProtoMap = cmd.NewCmdProtoMap() + r.clientCmdProtoMap = client_proto.NewClientCmdProtoMap() + r.clientCmdProtoMapRefValue = reflect.ValueOf(r.clientCmdProtoMap) r.messageQueue = messageQueue r.localMsgOutput = make(chan *ProtoMsg, 1000) r.createSessionChan = make(chan *Session, 1000) diff --git a/gate/net/proto_endecode.go b/gate/net/proto_endecode.go index c2def84b..11a62641 100644 --- a/gate/net/proto_endecode.go +++ b/gate/net/proto_endecode.go @@ -1,7 +1,11 @@ package net import ( + "reflect" + + "hk4e/common/config" "hk4e/pkg/logger" + "hk4e/pkg/object" "hk4e/protocol/cmd" "hk4e/protocol/proto" @@ -22,6 +26,33 @@ type ProtoMessage struct { func (k *KcpConnectManager) protoDecode(kcpMsg *KcpMsg) (protoMsgList []*ProtoMsg) { protoMsgList = make([]*ProtoMsg, 0) + if config.CONF.Hk4e.ClientProtoProxyEnable { + clientCmdId := kcpMsg.CmdId + clientProtoData := kcpMsg.ProtoData + cmdName := k.clientCmdProtoMap.GetClientCmdNameByCmdId(clientCmdId) + clientProtoObj := k.clientCmdProtoMapRefValue.MethodByName( + "GetClientProtoObjByCmdName", + ).Call([]reflect.Value{reflect.ValueOf(cmdName)})[0].Interface().(pb.Message) + err := pb.Unmarshal(clientProtoData, clientProtoObj) + if err != nil { + logger.Error("unmarshal client proto error: %v", err) + return protoMsgList + } + serverCmdId := k.serverCmdProtoMap.GetCmdIdByCmdName(cmdName) + serverProtoObj := k.serverCmdProtoMap.GetProtoObjByCmdId(serverCmdId) + err = object.CopyProtoBufSameField(serverProtoObj, clientProtoObj) + if err != nil { + logger.Error("copy proto obj error: %v", err) + return protoMsgList + } + serverProtoData, err := pb.Marshal(serverProtoObj) + if err != nil { + logger.Error("marshal server proto error: %v", err) + return protoMsgList + } + kcpMsg.CmdId = serverCmdId + kcpMsg.ProtoData = serverProtoData + } protoMsg := new(ProtoMsg) protoMsg.ConvId = kcpMsg.ConvId protoMsg.CmdId = kcpMsg.CmdId @@ -130,11 +161,36 @@ func (k *KcpConnectManager) protoEncode(protoMsg *ProtoMsg) (kcpMsg *KcpMsg) { } else { kcpMsg.ProtoData = nil } + if config.CONF.Hk4e.ClientProtoProxyEnable { + serverCmdId := kcpMsg.CmdId + serverProtoData := kcpMsg.ProtoData + serverProtoObj := k.serverCmdProtoMap.GetProtoObjByCmdId(serverCmdId) + err := pb.Unmarshal(serverProtoData, serverProtoObj) + if err != nil { + logger.Error("unmarshal server proto error: %v", err) + } + cmdName := k.serverCmdProtoMap.GetCmdNameByCmdId(serverCmdId) + clientProtoObj := k.clientCmdProtoMapRefValue.MethodByName( + "GetClientProtoObjByCmdName", + ).Call([]reflect.Value{reflect.ValueOf(cmdName)})[0].Interface().(pb.Message) + err = object.CopyProtoBufSameField(clientProtoObj, serverProtoObj) + if err != nil { + logger.Error("copy proto obj error: %v", err) + return nil + } + clientProtoData, err := pb.Marshal(clientProtoObj) + if err != nil { + logger.Error("marshal client proto error: %v", err) + } + clientCmdId := k.clientCmdProtoMap.GetClientCmdIdByCmdName(cmdName) + kcpMsg.CmdId = clientCmdId + kcpMsg.ProtoData = clientProtoData + } return kcpMsg } func (k *KcpConnectManager) decodePayloadToProto(cmdId uint16, protoData []byte) (protoObj pb.Message) { - protoObj = k.cmdProtoMap.GetProtoObjByCmdId(cmdId) + protoObj = k.serverCmdProtoMap.GetProtoObjByCmdId(cmdId) if protoObj == nil { logger.Error("get new proto object is nil") return nil @@ -148,7 +204,7 @@ func (k *KcpConnectManager) decodePayloadToProto(cmdId uint16, protoData []byte) } func (k *KcpConnectManager) encodeProtoToPayload(protoObj pb.Message) (cmdId uint16, protoData []byte) { - cmdId = k.cmdProtoMap.GetCmdIdByProtoObj(protoObj) + cmdId = k.serverCmdProtoMap.GetCmdIdByProtoObj(protoObj) var err error = nil protoData, err = pb.Marshal(protoObj) if err != nil { diff --git a/pkg/object/object.go b/pkg/object/object.go index 44773dfa..0a805b63 100644 --- a/pkg/object/object.go +++ b/pkg/object/object.go @@ -3,12 +3,16 @@ package object import ( "bytes" "encoding/gob" + "encoding/json" "fmt" + "strings" "github.com/vmihailenco/msgpack/v5" + "google.golang.org/protobuf/encoding/protojson" + pb "google.golang.org/protobuf/proto" ) -func DeepCopy(dst, src any) error { +func FullDeepCopy(dst, src any) error { var buf bytes.Buffer err := gob.NewEncoder(&buf).Encode(src) if err != nil { @@ -33,6 +37,52 @@ func FastDeepCopy(dst, src any) error { return nil } +func CopyProtoBufSameField(dst, src pb.Message) error { + date, err := protojson.Marshal(src) + if err != nil { + return err + } + jsonObj := make(map[string]any) + err = json.Unmarshal(date, &jsonObj) + if err != nil { + return err + } + for { + jsonData, err := json.Marshal(jsonObj) + if err != nil { + return err + } + err = protojson.Unmarshal(jsonData, dst) + if err != nil { + if !strings.Contains(err.Error(), "unknown field") { + return err + } + split := strings.Split(err.Error(), "\"") + if len(split) != 3 { + return err + } + fieldName := split[1] + DeleteAllKeyNameFromStringAnyMap(jsonObj, fieldName) + continue + } else { + break + } + } + return nil +} + +func DeleteAllKeyNameFromStringAnyMap(src map[string]any, keyName string) { + for key, value := range src { + v, ok := value.(map[string]any) + if ok { + DeleteAllKeyNameFromStringAnyMap(v, keyName) + } + if key == keyName { + delete(src, key) + } + } +} + func ConvBoolToInt64(v bool) int64 { if v { return 1 diff --git a/pkg/reflection/struct.go b/pkg/reflection/struct.go index 1ee6f909..1d8c0689 100644 --- a/pkg/reflection/struct.go +++ b/pkg/reflection/struct.go @@ -11,7 +11,6 @@ func ConvStructToMap(value any) map[string]any { return nil } fieldNum := refType.NumField() - result := make(map[string]any) nameList := make([]string, 0) for i := 0; i < fieldNum; i++ { nameList = append(nameList, refType.Field(i).Name) @@ -20,6 +19,7 @@ func ConvStructToMap(value any) map[string]any { if refValue.Kind() == reflect.Ptr { refValue = refValue.Elem() } + result := make(map[string]any) for i := 0; i < fieldNum; i++ { result[nameList[i]] = refValue.FieldByName(nameList[i]).Interface() } @@ -77,3 +77,59 @@ func CopyStructField(dst any, src any, fieldName string) bool { } return true } + +func CopyStructSameField(dst any, src any) bool { + // dst + dstRefType := reflect.TypeOf(dst) + if dstRefType.Kind() != reflect.Ptr { + return false + } + dstRefType = dstRefType.Elem() + if dstRefType.Kind() != reflect.Struct { + return false + } + dstRefValue := reflect.ValueOf(dst) + if dstRefValue.Kind() != reflect.Ptr { + return false + } + dstRefValue = dstRefValue.Elem() + // src + srcRefType := reflect.TypeOf(src) + if srcRefType.Kind() != reflect.Ptr { + return false + } + srcRefType = srcRefType.Elem() + if srcRefType.Kind() != reflect.Struct { + return false + } + srcRefValue := reflect.ValueOf(src) + if srcRefValue.Kind() != reflect.Ptr { + return false + } + srcRefValue = srcRefValue.Elem() + // copy + fieldNum := srcRefType.NumField() + for i := 0; i < fieldNum; i++ { + srcFieldType := srcRefType.Field(i) + if !srcFieldType.IsExported() { + continue + } + fieldName := srcFieldType.Name + dstFieldType, ok := dstRefType.FieldByName(fieldName) + if !ok { + continue + } + srcField := srcRefValue.FieldByName(fieldName) + dstField := dstRefValue.FieldByName(fieldName) + if srcField.Kind() == reflect.Ptr { + dstField.Set(reflect.New(dstFieldType.Type.Elem())) + CopyStructSameField(dstField.Interface(), srcField.Interface()) + continue + } + if dstField.Type() != reflect.TypeOf(srcField.Interface()) { + return false + } + dstField.Set(reflect.ValueOf(srcField.Interface())) + } + return true +} diff --git a/pkg/reflection/struct_test.go b/pkg/reflection/struct_test.go new file mode 100644 index 00000000..e5c643e0 --- /dev/null +++ b/pkg/reflection/struct_test.go @@ -0,0 +1,49 @@ +package reflection + +import ( + "fmt" + "testing" +) + +type XXX struct { + Time int64 + Date string +} + +type YYY struct { + Ping uint16 +} + +type AAA struct { + Name string + UserId uint32 + A uint8 + X *XXX + Y YYY +} + +type BBB struct { + Name string + UserId uint32 + B uint8 + X *XXX + Y YYY +} + +func TestCopyStructSameField(t *testing.T) { + aaa := &AAA{ + Name: "flswld", + UserId: 100000001, + A: 111, + X: &XXX{ + Time: 150405, + Date: "2006-01-02", + }, + Y: YYY{ + Ping: 999, + }, + } + bbb := new(BBB) + ok := CopyStructSameField(bbb, aaa) + fmt.Println(ok) +} diff --git a/protocol/cmd/cmd_id_proto_obj_map.go b/protocol/cmd/cmd_id_proto_obj_map.go index 3157cd3b..e9c144cf 100644 --- a/protocol/cmd/cmd_id_proto_obj_map.go +++ b/protocol/cmd/cmd_id_proto_obj_map.go @@ -13,6 +13,8 @@ type CmdProtoMap struct { cmdIdProtoObjMap map[uint16]reflect.Type protoObjCmdIdMap map[reflect.Type]uint16 cmdDeDupMap map[uint16]bool + cmdIdCmdNameMap map[uint16]string + cmdNameCmdIdMap map[string]uint16 } func NewCmdProtoMap() (r *CmdProtoMap) { @@ -20,6 +22,8 @@ func NewCmdProtoMap() (r *CmdProtoMap) { r.cmdIdProtoObjMap = make(map[uint16]reflect.Type) r.protoObjCmdIdMap = make(map[reflect.Type]uint16) r.cmdDeDupMap = make(map[uint16]bool) + r.cmdIdCmdNameMap = make(map[uint16]string) + r.cmdNameCmdIdMap = make(map[string]uint16) r.registerAllMessage() return r } @@ -268,30 +272,52 @@ func (c *CmdProtoMap) registerMessage(cmdId uint16, protoObj pb.Message) { } else { c.cmdDeDupMap[cmdId] = true } + refType := reflect.TypeOf(protoObj) // cmdId -> protoObj - c.cmdIdProtoObjMap[cmdId] = reflect.TypeOf(protoObj) + c.cmdIdProtoObjMap[cmdId] = refType // protoObj -> cmdId - c.protoObjCmdIdMap[reflect.TypeOf(protoObj)] = cmdId + c.protoObjCmdIdMap[refType] = cmdId + cmdName := refType.Elem().Name() + // cmdId -> cmdName + c.cmdIdCmdNameMap[cmdId] = cmdName + // cmdName -> cmdId + c.cmdNameCmdIdMap[cmdName] = cmdId } -func (c *CmdProtoMap) GetProtoObjByCmdId(cmdId uint16) (protoObj pb.Message) { - protoObjTypePointer, ok := c.cmdIdProtoObjMap[cmdId] - if !ok { +func (c *CmdProtoMap) GetProtoObjByCmdId(cmdId uint16) pb.Message { + refType, exist := c.cmdIdProtoObjMap[cmdId] + if !exist { logger.Error("unknown cmd id: %v", cmdId) - protoObj = nil - return protoObj + return nil } - protoObjInst := reflect.New(protoObjTypePointer.Elem()) - protoObj = protoObjInst.Interface().(pb.Message) + protoObjInst := reflect.New(refType.Elem()) + protoObj := protoObjInst.Interface().(pb.Message) return protoObj } -func (c *CmdProtoMap) GetCmdIdByProtoObj(protoObj pb.Message) (cmdId uint16) { - var ok = false - cmdId, ok = c.protoObjCmdIdMap[reflect.TypeOf(protoObj)] - if !ok { +func (c *CmdProtoMap) GetCmdIdByProtoObj(protoObj pb.Message) uint16 { + cmdId, exist := c.protoObjCmdIdMap[reflect.TypeOf(protoObj)] + if !exist { logger.Error("unknown proto object: %v", protoObj) - cmdId = 0 + return 0 + } + return cmdId +} + +func (c *CmdProtoMap) GetCmdNameByCmdId(cmdId uint16) string { + cmdName, exist := c.cmdIdCmdNameMap[cmdId] + if !exist { + logger.Error("unknown cmd id: %v", cmdId) + return "" + } + return cmdName +} + +func (c *CmdProtoMap) GetCmdIdByCmdName(cmdName string) uint16 { + cmdId, exist := c.cmdNameCmdIdMap[cmdName] + if !exist { + logger.Error("unknown cmd name: %v", cmdName) + return 0 } return cmdId }