Files
bl/logic/service/player/pack.go
昔念 aefef6a456
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
fix(player): 修复玩家ID记录错误

当处理客户端消息时发生panic错误,日志中记录的玩家ID应该是当前客户端数据中的
玩家ID(cd.Player.Info.UserID),而不是错误引用的h.Player.Info.UserID。

这确保了错误日志能够正确关联到实际出错的玩家。
```
2026-03-04 01:38:24 +08:00

336 lines
8.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package player
import (
"blazing/common/socket/errorcode"
"blazing/cool"
"blazing/logic/service/common"
"encoding/binary"
"encoding/hex"
"sync"
"context"
"bytes"
"fmt"
"reflect"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/gogf/gf/v2/os/glog"
"github.com/lunixbochs/struc"
"github.com/panjf2000/gnet/v2"
"github.com/valyala/bytebufferpool"
)
// getUnderlyingValue 递归解析reflect.Value解包指针、interface{}到底层具体类型
func getUnderlyingValue(val reflect.Value) (reflect.Value, error) {
for {
switch val.Kind() {
// 解包指针:获取指针指向的值
case reflect.Ptr:
if val.IsNil() {
return reflect.Value{}, nil
}
val = val.Elem()
// 解包interface{}:获取接口包裹的动态值
case reflect.Interface:
if val.IsNil() {
return reflect.Value{}, nil
}
val = val.Elem()
// 非指针/接口类型,终止递归
default:
return val, nil
}
}
}
// XORDecryptU 优化后的异或解密:减少内存分配,支持复用缓冲区
// XORDecryptU 基于bytebufferpool优化的异或解密函数
// 保留原有接口无侵入式优化高频调用下大幅减少内存分配和GC
func XORDecryptU(encryptedData []byte, key uint32) []byte {
if len(encryptedData) == 0 {
return []byte{}
}
// 1. 栈上分配密钥字节数组无GC压力保留原优化
var keyBytes [4]byte
binary.BigEndian.PutUint32(keyBytes[:], key)
keyLen := len(keyBytes)
// 2. 从bytebufferpool获取池化缓冲区替代make分配
buf := bytebufferpool.Get()
defer bytebufferpool.Put(buf) // 函数结束自动归还缓冲区到池
// 3. 调整缓冲区长度,匹配待解密数据(避免扩容)
buf.B = buf.B[:0] // 清空原有数据,保留底层数组
if cap(buf.B) < len(encryptedData) {
// 若缓冲区容量不足直接扩容bytebufferpool会自动管理
buf.B = make([]byte, len(encryptedData))
} else {
// 容量足够,直接调整长度
buf.B = buf.B[:len(encryptedData)]
}
// 4. 核心异或解密逻辑直接操作buf.B无额外内存分配
decrypted := buf.B
for i, b := range encryptedData {
decrypted[i] = b ^ keyBytes[i%keyLen]
}
// 5. 拷贝结果(关键:避免返回池化缓冲区,防止被后续调用覆盖)
result := make([]byte, len(decrypted))
copy(result, decrypted)
return result
}
// 原 OnEvent 改为仅负责投递消息到通道
func (cd *ClientData) OnEvent(data common.TomeeHeader) {
// 非阻塞发送,避免通道满时阻塞 eventloop
select {
case cd.MsgChan <- data:
default:
cool.Logger.Error(context.TODO(), "消息通道已满,丢弃消息", data.UserID, data.CMD)
// 通道满时的降级处理:记录日志,避免消息丢失(可选)
//cool.Logger.Warn(context.TODO(), "消息通道已满,丢弃消息", data.UserID, data.CMD)
}
}
// 消费消息的协程:处理业务逻辑,不阻塞 eventloop
func (cd *ClientData) consumeMsg() {
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
if cd.Player != nil {
if cd.Player.Info != nil {
cool.Logger.Error(context.TODO(), "panic 错误:", cool.Config.ServerInfo.OnlineID, cd.Player.Info.UserID, err)
} else {
cool.Logger.Error(context.TODO(), "panic 错误:", cool.Config.ServerInfo.OnlineID, err)
}
} else {
cool.Logger.Error(context.TODO(), "panic 错误:", err)
}
}
}()
for header := range cd.MsgChan {
// 执行原有的 OnEvent 逻辑
cd.handleBizLogic(header)
}
}
// 重写
// 遍历结构体方法并执行RECV_cmd
func (h *ClientData) handleBizLogic(data common.TomeeHeader) {
if data.CMD > 1001 {
if h.Player == nil {
fmt.Println(data.UserID, "账号未注册")
return
}
if h.Player.Info == nil {
fmt.Println(data.UserID, "未创建角色")
return
}
if data.Data != nil {
data.Data = XORDecryptU(data.Data, h.Player.Hash)
}
}
if cool.Config.ServerInfo.IsDebug != 0 {
fmt.Println("接收数据", data.UserID, data.CMD)
}
if data.UserID == 0 {
return
}
cmdlister, ok := cool.CmdCache[data.CMD]
if !ok {
// glog.Debug(context.Background(), data.UserID, data.CMD, "cmd未注册")
return //TODO 待实现cmd未注册
}
params := []reflect.Value{}
//funct := cmdlister.Type().NumIn()
// 如果需要可设置的变量(用于修改值),创建指针并解引用
ptrValue := reflect.New(cmdlister.Req)
tt1 := ptrValue.Elem().Addr().Interface()
// fmt.Println(tt1)
err := struc.Unpack(bytes.NewBuffer(data.Data), tt1)
playerconn, cok := h.Conn.Context().(*ClientData)
if !cok { //如果链接断开,就返回
return
}
if err != nil {
cool.Logger.Error(context.Background(), data.UserID, data.CMD, "解包失败,", err, hex.EncodeToString(data.Data))
//fmt.Println(data.UserID, data.CMD, "解包失败,", hex.EncodeToString(data.Data))
data.Result = uint32(errorcode.ErrorCodes.ErrSystemProcessingError)
playerconn.SendPack(data.Pack(nil))
return
}
ptrValue1 := ptrValue.Elem().Addr()
// 设置 Name 字段
nameField := ptrValue.Elem().Field(0) //首个为header
nameField.Set(reflect.ValueOf(data))
if data.CMD > 1001 { //if cmdlister.Type().In(1) == reflect.TypeOf(&Player{}) {
//t := GetPlayer(c, data.UserID)
// fmt.Println(data.CMD, "接收 变量的地址 ", &t.Info, t.Info.UserID)
params = append(params, ptrValue1, reflect.ValueOf(h.Conn.Context().(*ClientData).Player))
} else {
params = append(params, ptrValue1, reflect.ValueOf(h.Conn))
}
ret := cmdlister.Func.Call(params)
if len(ret) <= 0 { //如果判断没有参数,那就说明这个包没有返回参数
return
}
aa, ok := ret[1].Interface().(errorcode.ErrorCode) //判断错误
data.Result = uint32(aa)
if aa == -1 {
return
}
if ok && aa != 0 { //这里实现回复错误包
glog.Info(context.Background(), data.UserID, data.CMD, aa.Code())
playerconn.SendPack(data.Pack(nil))
return
}
t1 := data.Pack(ret[0].Interface())
playerconn.SendPack(t1)
}
type ClientData struct {
IsCrossDomain sync.Once //是否跨域过
Player *Player //客户实体
//Mu sync.RWMutex
ERROR_CONNUT int
Wsmsg *WsCodec
Conn gnet.Conn
//LF *gqueue.TQueue[common.TomeeHeader]
//SaveL sync.Once //保存锁
MsgChan chan common.TomeeHeader
//SaveDone chan struct{}
}
func NewClientData(c gnet.Conn) *ClientData {
// 创建事件处理器
// 创建消费端串行处理的Lockfree
cd := &ClientData{
Conn: c,
Wsmsg: &WsCodec{},
MsgChan: make(chan common.TomeeHeader, 100),
}
//cd.LF = make(chan common.TomeeHeader, 8)
// cd.LF = gqueue.NewTQueue[common.TomeeHeader](1)
// go func() {
// for {
// select {
// case queueItem := <-cd.LF.C:
// cd.OnEvent(queueItem)
// }
// }
// }()
// cd.LF = lockfree.NewLockfree(
// 8,
// cd,
// lockfree.NewSleepBlockStrategy(time.Millisecond),
// )
// // // 启动Lockfree
// if err := cd.LF.Start(); err != nil {
// panic(err)
// }
// 启动每个连接的独立消费协程
go cd.consumeMsg()
// // // 启动Lockfree
// // if err := cd.LF.Start(); err != nil {
// // panic(err)
// // }
return cd
}
// XORDecrypt 异或解密函数密钥改为uint32版本
// 核心逻辑将uint32密钥拆分为4字节数组大端序适配AS3二进制处理习惯循环与加密数据异或
// 参数:
//
// encryptedData - 待解密的字节数组
// key - 32位无符号整数密钥替代原字符串密钥
//
// 返回值:解密后的字节数组
func XORDecrypt(encryptedData []byte, keyStr string) []byte {
if len(encryptedData) == 0 || keyStr == "" {
return []byte{}
}
// 1. 将密钥字符串转换为UTF-8字节数组对应AS3的writeUTFBytes(_arg_2)
keyBytes := []byte(keyStr) // Go中string转[]byte默认是UTF-8编码与AS3的writeUTFBytes一致
keyLen := len(keyBytes)
if keyLen == 0 {
return encryptedData // 空密钥不加密,直接返回
}
// 2. 执行异或操作(与加密逻辑一致,异或两次还原数据)
decrypted := make([]byte, len(encryptedData))
for i, b := range encryptedData {
// 循环复用密钥字节(索引取模)
keyIndex := i % keyLen
decrypted[i] = b ^ keyBytes[keyIndex]
}
return decrypted
}
func (p *ClientData) SendPack(b []byte) error {
cli, ok := p.Conn.Context().(*ClientData)
if !ok {
return fmt.Errorf("链接错误,取消发包")
}
if cli.Wsmsg == nil {
return fmt.Errorf("ws空")
}
if cli.Wsmsg.Upgraded {
// This is the echo server
wsutil.WriteServerMessage(p.Conn, ws.OpBinary, b)
} else {
p.Conn.AsyncWrite(b, nil)
}
return nil
}