实现网关服务器不同版本客户端协议代理功能

This commit is contained in:
flswld
2022-12-25 00:42:07 +08:00
parent f4614b3df6
commit e96e9e3d3c
11 changed files with 374 additions and 36 deletions

2
.gitignore vendored
View File

@@ -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

View File

@@ -1,6 +1,7 @@
[hk4e]
kcp_addr = "127.0.0.1"
kcp_port = 22103
client_proto_proxy_enable = false
[logger]
level = "DEBUG"

View File

@@ -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 消息队列

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}