Dump自动上传、自动解析、自动返送

***********************前提:大通废话可以跳过*************************

Dump文件是C++程序发生异常时,保存程序运行状态的信息的二进制文件。在大型项目开发过程中,尤其是游戏项目,dump文件是调试异常程序的重要资源,其中包含的堆栈调用信息数据,是会极大地方便开发人员来排查错误并进行修正。

公司项目一开始在C++工程中添加了dump生成代码,以及开发了一个内部使用的dump解析网址。日常使用场景是:玩游戏,程序崩溃产生dump,打开解析网址,选择对应的游戏版本号、分支以及dump文件,提交(!nice!)然后将解析出的网址发给开发人员,这样开发人员就可以来排错改bug了。

BUT!!!实际上很多策划美术同学进入游戏产生了崩溃,只要不影响他们干自己的活,这个dump就静静地待在他们的文件夹里,不为人所知,那么其包含的珍贵堆栈信息也就随时间消散了。。。

针对这个问题,行政上的督促事半功倍,自动化的工具和流程才值得依赖。sososo博主针对公司项目情况,将该流程由手动提交优化成自动提交,致力于拯救那些消散在时间长河里的dump文件😎

***********************进入正题*************************

1、客户端采用socket方式连接服务器 c#实现

public ClientConnect()
{
}

// 初始化
public bool Init_Socket()
{
    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    return Connect();
}

// 连接服务器
private bool Connect()
{
    if (_socket != null)
    {
        try
        {
            IPAddress remoteHostIP = IPAddress.Parse(_url);
            _socket.Connect(new IPEndPoint(remoteHostIP, _port));
            Console.WriteLine($"_socket.Connected:{_socket.Connected}");
            _ready = true; 
        }
        catch (Exception ex)
        {
            Console.WriteLine("服务器连接失败:" + ex.ToString());
            Console.ReadKey();
        }
    }
    return _ready;
}



// 关闭连接
public void Disconnect()
{
    Console.WriteLine("准备关闭_socket套接字...");
    if (_socket.Connected)
        _socket.Shutdown(SocketShutdown.Both);
    _socket?.Close();//_socket不为空则Close()=_socket.Close()
}

2、传输多个二进制及字符串类型的数据

注意:数据传输流的结束以及数据之间的分割

 // 传输数据
 public void Trans(string file, string rev, int vers, string auther, string txt)
 {
     byte[] versBytes = BitConverter.GetBytes(vers);
     byte[] revBytes = BitConverter.GetBytes(int.Parse(rev));
     byte[] autBytes = Encoding.UTF8.GetBytes(auther);
     byte[] txtBytes = Encoding.UTF8.GetBytes(txt);
     byte[] fileData = File.ReadAllBytes(file);

// 数据分割标志
     byte[] _1rdMaker = Encoding.UTF8.GetBytes("DOB");
     byte[] _2rdMaker = Encoding.UTF8.GetBytes("EOF");
     byte[] _3rdMaker = Encoding.UTF8.GetBytes("END");

     int totalLength = revBytes.Length + versBytes.Length + autBytes.Length +
         txtBytes.Length + _1rdMaker.Length + _2rdMaker.Length + fileData.Length;
     // 创建合并后的字节数组
     byte[] combinedBytes = new byte[totalLength];
     // 使用BlockCopy合并所有的字节数组
     int offset = 0;
     Buffer.BlockCopy(versBytes, 0, combinedBytes, offset, versBytes.Length == 1 ? 1 : 1);

     offset += versBytes.Length == 1 ? 1 : 1;
     Buffer.BlockCopy(revBytes, 0, combinedBytes, versBytes.Length == 1 ? 1 : 1, revBytes.Length == 4 ? 4 : 4);

     offset += revBytes.Length == 4 ? 4 : 4;
     Buffer.BlockCopy(fileData, 0, combinedBytes, offset, fileData.Length);

     offset += fileData.Length;
     Buffer.BlockCopy(_1rdMaker, 0, combinedBytes, offset, _1rdMaker.Length);

     offset += _1rdMaker.Length;
     Buffer.BlockCopy(autBytes, 0, combinedBytes, offset, autBytes.Length);

     offset += autBytes.Length;
     Buffer.BlockCopy(_2rdMaker, 0, combinedBytes, offset, _2rdMaker.Length);

     offset += _2rdMaker.Length;
     Buffer.BlockCopy(txtBytes, 0, combinedBytes, offset, txtBytes.Length);

     if (_socket.Connected)
     {
         try
         {
             _socket.Send(combinedBytes, combinedBytes.Length, SocketFlags.None);
             // 告知服务器文件流已传输完毕
             _socket.Send(_3rdMaker);
             Console.WriteLine("文件上传成功");
             _finish = true;
         }
         catch (SocketException ex)
         {
             Console.WriteLine("文件传输失败:" + ex.Message);
             _finish = true;
         }
     }
     else
     {
         Console.WriteLine("服务器未连接...请尝试先开启服务器连接");
         _finish = true;
         return;
     }
 }

3、服务器采用socket方式接收响应 nodejs实现

const csharpServer = net.createServer((socket) => {
    socket.name = socket.remoteAddress + ":" + socket.remotePort;
    console.log(new Date().toLocaleString(),`客户端已连接:${socket.name}`);
// 连接后的业务逻辑代码


    socket.on('data', (data) => {
        console.log(new Date().toLocaleString(),'接收到数据');
// 接收数据后的业务逻辑代码

    });


    socket.on('close', () => {
        console.log(new Date().toLocaleString(),`客户端已断开:${socket.name}`);
// 关闭连接后的业务逻辑代码

    });


    socket.on('error', (err) => {
        console.error(new Date().toLocaleString(),'Socket error:', err.message);
// 连接出错后的业务逻辑代码

    });
});



const clientPort = /*端口号*/;
csharpServer.listen(clientPort, () => {
    console.log(`CSharp服务器已启动,正在监听端口 ${clientPort}`);
});

4、数据接收后的处理

该部分分为两步:1、数据分割  2、数据解析

        1、数据分割

这个比较简单,按照之前设置的分隔符分割即可

    let receivedData = Buffer.from([]);
    const _1rdMarker = Buffer.from('DOB'); 
    const _2rdMarker = Buffer.from('EOF');
    const _3rdMarker = Buffer.from('END');

    socket.on('data', (data) => {
        console.log(new Date().toLocaleString(),'接收到数据');
        receivedData = Buffer.concat([receivedData, data]);

        if (isCompleteFile(receivedData)) {
// 接收到结束标志后 清空数据缓存区
            processReceivedData(socket, receivedData);
            receivedData = Buffer.alloc(0);
        }
    });

    function isCompleteFile(data) {
        return data.includes('END');
    }

    function processReceivedData(socket, receivedData) {

        const _1rdIndex = receivedData.lastIndexOf(_1rdMarker);
        const _2rdIndex = receivedData.lastIndexOf(_2rdMarker);
        const _3rdIndex = receivedData.lastIndexOf(_3rdMarker);
 
        let vers = receivedData[0];//类型
        let rev = receivedData.readInt32LE(1);//版本
        let fileData = receivedData.slice(5, _1rdIndex);//dump文件
        let user = receivedData.slice(_1rdIndex + 3, _2rdIndex);
        let txt = receivedData.slice(_2rdIndex + 3, _3rdIndex);

        // 存储dump
        const filePath = "你的路径";
        fs.writeFileSync(filePath, fileData, { flag: 'a+' }, (err) => {
            if (err) {
                console.error('写文件出错:', err);
            } else {
                console.log('文件已保存为', filePath);
            }
        });
        // 处理dump文件
        let result = RunWinDbg(filePath, vers, rev);

        2、数据解析

这个比较麻烦。建议去了解一下windbg。一般都是使用windbg工具解析dump,这个需要对应的工程符号表。所以在写业务逻辑代码前,要和开发人员确定好工程符号表的存放路径。主要是流程上的确认。

const command = `${windbgPath}`; // 你的windbg.exe路径
    const args = [
        '-z',
        dumpPath, // dump文件存放路径
        '-c',
        '.ecxr;kp;',
        '-loga',
        outPath, // 解析结果存放路径
        '-c',
        'q'
      ];
    const options = {
        timeout: time,
        cwd: releasePath // 你的符号表路径
      };

// 记录此次dump的版本信息
    fs.writeFileSync(outPath, svr_type[vers - 1] + "svn版本:" + rev + '\r\n');
    var url = "服务器ip与端口";

// 不同版本工程对应不同版本符号表 所以注意svn更到相应的版本 
    executeWindowsCommand('svn', ['cleanup', releasePath], {})
    executeWindowsCommand('svn', ['revert','-R', releasePath], {})
    executeWindowsCommand('svn', ['up', '-r', rev, releasePath], {})

    // 同步windbg
    if (exctorCmdSync(command, args,options)) {
        return "http://" + `${url}` + "/" + filename + ".txt";
    }
    return "false"

5、结果的发送

这部分很简单,是有现成的模板。主要看你是想发送到哪。贴主是发送到企业微信群聊。

async function sendToWechatGroup(param1, param2, param3) {
  // 拼接要发送的内容
  const content = "你自己设定";
  const webhookUrl = '企业微信群机器人webhook';

  // 出错情况的业务逻辑

  // 完美情况
  try {
    const response = await axios.post(webhookUrl, {
      msgtype: 'text',
      text: {
        content: content,
      },
    });

// 发送异常处理
    if (response.status === 200 && response.data.errcode === 0) {
      console.log(new Date().toLocaleString(),'消息发送成功');
    } else {
      console.error(new Date().toLocaleString(),'消息发送失败');
    }
  } catch (error) {
    console.error(new Date().toLocaleString(),'发送请求出错:', error);
  }
}

6、效果展示

*******************************一些碎碎念**********************************

贴主一开始接手时,是将解析链接发送到群聊。但实际生产中,由机器人同一发送的消息,找不到提交人,这就导致开发人员在排查时都不知道问谁,提交了的同学也不知道是不是自己提交的。于是乎采用了“svn auth”来获取当前同学的姓名。

!!!但竟然有同学没有安装svn命令行工具,导致客户端连接时啪唧一下就断了,于是乎贴主在服务器端采用“socket.name = socket.remoteAddress + ":" + socket.remotePort;”获取当前同学的电脑ip,找运维大哥定位是哪位小可爱,催着ta安装svn命令行。

终于当同学们都准备好前置工具可以无痛自动提交dump后!!!又有聪明的小可爱提出顺便加上崩溃时的游戏场景描述。

好好好有什么需求我都满足放马过来吧!!!最后我在客户端工程新建了Winform项目,增加一个文本描述框记录文本信息。

一通操作下来,竟然有了从0到1的产品设计的赶脚。最后看着群聊里机器人发送的一个又一个dump信息,有种修修的欣慰感,但很快转瞬即逝,毕竟这属于幸灾乐祸了hhhhhhhhhhhhhhh

ps::该帖意在记录与分享自己工作中解决的问题,以及一些疯癫的碎碎念。如果可能帮助到相似情景的广大同胞们,那就真是太棒了!!!

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值