游戏服务端之C++游戏服务端防崩溃

在游戏开发当中,不用我多说了,游戏的稳定性是相当重要的。

为了追求游戏的稳定性,很多开发者都会选择脚本语言作为游戏的主要开发语言。因为使用脚本语言,即使游戏出现重大的bug,由于脚本支持热更的天然优势,使开发者更迅速地解决问题;而且脚本的良好容错性,也会使游戏系统不会轻易崩溃。但是,一些即时性游戏需要更高性能的,开发者可能会选择C/C++作为开发语言,但是怎样来保证游戏的稳定性呢?

关键词是 信号

其实在Linux系统中防崩溃的相关内容,我已经在以前的一篇博文粗略提及过。下面再更详细地描述一下。

#ifndef SHOWCRASH_H
#define SHOWCRASH_H

#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <execinfo.h>
#include <string>
#include "globalvariable.h"
#include "globalfunction.h"
using namespace std;


class ShowCrash
{
public:
    ShowCrash();

    static void CrashFunction(int);

    static const int MAX_JMP_BUF = 16;

    static const int MAX_CALLSTACK_DEPTH = 32;

    enum buffername
    {
        BUF_MAIN ,
        BUF_COUNT
    };


private:


};

extern int buf_index;

#define SETJMP(index)\
    setjmp(buff[index]);\
    buf_index = index;\


extern ShowCrash g_showcrash;
extern jmp_buf buff[ShowCrash::MAX_JMP_BUF];

#endif

#include "showcrash.h"



ShowCrash g_showcrash;
jmp_buf buff[ShowCrash::MAX_JMP_BUF];
int buf_index;

ShowCrash::ShowCrash()
{
    struct sigaction act, oact;
    act.sa_handler = CrashFunction;
    sigemptyset(&act.sa_mask); //娓呯┖姝や俊鍙烽泦
    act.sa_flags = SA_NODEFER;
    sigaction(SIGINT, &act, &oact);
    sigaction(SIGABRT, &act, &oact);
    sigaction(SIGSEGV, &act, &oact);
    sigaction(SIGFPE, &act, &oact);
    sigaction(SIGBUS, &act, &oact);
    sigaction(SIGTRAP,&act,&oact);
}

void ShowCrash::CrashFunction(int)
{
    static void *traceback[MAX_CALLSTACK_DEPTH];
    static string cmd;
    if (cmd.length() <= 0)
    {
        ProgramName name;
        GLOBALFUNCTION::GetProgramName(name);
        string temp = name;
        cmd  =  "addr2line -f -e " + temp;
    }

    FILE *fp = popen(cmd.c_str(), "w");
    int depth = backtrace(traceback, MAX_CALLSTACK_DEPTH);
    for (int i = 0; i < depth && i < MAX_CALLSTACK_DEPTH; i++)
    {
        fprintf(fp, "%p\n", traceback[i]);
    }
    fclose(fp);
    longjmp(buff[buf_index],1);
}


下面是测试用例

#include <stdio.h>
#include <time.h>

#include "globalvariable.h"
#include "luaengine.h"
#include "gamesocket.h"
#include "log.h"
#include "dll.h"
#include "MyDll.h"
#include "gametime.h"
#include "frame.h"
#include "datatable.h"
#include "showcrash.h"
#include "globalfunction.h"

int main()
{
    int i =  -3;
    SETJMP(ShowCrash::BUF_MAIN);
    while (i < 2)
    {
        i++;
        printf("%d\n",10/i);
    }
    return 0;

}

在上面的测试用例可以看出,如果按一般的情况,当i加到0的时候,10会除0崩溃,程序就会终止执行。

但是当我加一句SETJMP(ShowCrash::BUF_MAIN);程序就会发生奇妙的变化!


程序不仅继续执行了,而且还输出了出错的行数,在main.cpp的23行中出错。

为什么会这样呢?

原因是这样的,当程序发生致命的错误时,系统会向程序发送信号,而这些信号的默认处理是终止程序的的继续执行。

例如:

SIGABRT 异常终止

SIGINT 终端中断符(在Linux中经常在终端使用的Ctrl + c)

SIGSEGV 无效内存引用

SIGFPE 算术异常(就是我上面的测试用例了)

等等

但是,我们可以捕获系统的信号,捕获了信号我们不让程序退出,而是做一些其它的处理,例如打印出错的位置。这样就提高了程序的稳定性了。

单单上面的代码是不能正常执行的,因为里面还有这样的一个函数GLOBALFUNCTION::GetProgramName(name);,这个主要是获得当前程序的程序名。

#include "globalfunction.h"
namespace GLOBALFUNCTION
{
    void GameInit()
    {

    }


     void GetProgramName(ProgramName name)
    {
        static  char filename[PROGRAMNAMELEN]={'\0'};
        if (filename[0] != '\0')
        {
            memcpy(name, filename, PROGRAMNAMELEN * sizeof(char));
        }
        char sysfile[15] = "/proc/self/exe";

        if ( -1 == readlink( sysfile,filename,PROGRAMNAMELEN ) )
        {
           memcpy(filename,"GameEgine",sizeof("GameEngine"));
        }
        memcpy(name, filename, PROGRAMNAMELEN * sizeof(char));
    }

}

加上这个函数,就可以正常执行了。

最后总结一下:(PS:详细的关键字,自己搜索一下)

1、为了不让程序崩溃,主要是signal,在构造函数里面已经做了

2、打印出错信息,在CrashFunction中,主要通过管道调用addr2line和调用backtrace来实现

3、其实即使程序做了以上两步,程序还是会退出的,程序不退出的关键在setjmp和longjmp

4、其实上面的代码有一个巧妙的用法就是,ShowCrash g_showcrash;,这样就不会重复包含,而且也能初始化signal。

猜你喜欢

转载自blog.csdn.net/a374826954/article/details/17175113