[TDR表][C++ SDK]查询表记录总数

1. 接口说明

查询表记录总数(example路径:examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count)

2. 版本要求

无特殊要求,所有版本都提供了该接口。

3. 准备工作

参见准备工作文档,完成使用该接口前的准备工作,并创建如下TDR Generic表,并使用TDR工具将XML表转换为C++代码。PLAYERONLINECNT表描述文件table_test.xml

<?xml version="1.0" encoding="GBK" standalone="yes" ?>
<metalib name="tcaplus_tb" tagsetversion="1" version="1">
<struct name="PLAYERONLINECNT" version="1" primarykey="TimeStamp,GameSvrID" splittablekey="TimeStamp">
    <entry name="TimeStamp"         type="uint32"     desc="单位为分钟" />
    <entry name="GameSvrID"         type="string"     size="64" />
    <entry name="GameAppID"         type="string"     size="64" desc="gameapp id" />
    <entry name="OnlineCntIOS"      type="uint32"     defaultvalue="0" desc="ios在线人数" />
    <entry name="OnlineCntAndroid"  type="uint32"     defaultvalue="0" desc="android在线人数" />
    <entry name="BinaryLen"         type="smalluint" defaultvalue="1" desc="数据来源数据长度;长度为0时,忽略来源检查"/>
    <entry name="binary"            type="tinyint"     desc="二进制" count= "1000" refer="BinaryLen" />
    <entry name="binary2"           type="tinyint"     desc="二进制2" count= "1000" refer="BinaryLen" />
    <entry name="strstr"            type="string"     size="64" desc="字符串"/>
    <index name="index_id"  column="TimeStamp"/>
</struct>
</metalib>

准备工作完成后,将会获得以下信息,这些信息在使用SDK时会被用到:

  1. 目录服务器地址列表
  2. 业务ID
  3. 业务访问密码
  4. 游戏区ID
  5. 数据表名

4. 示例代码

4.1 异步调用示例代码

示例代码的基本执行过程:

  1. 定义表配置参数
  2. 创建日志句柄;
  3. 创建客户端;
  4. 发送请求;
  5. 处理响应;
  6. 销毁客户端。
  7. 异步框架;

第1,2,3,6,7步是所有示例的通用代码,重点关注第4步和第5步,发送请求和处理响应

4.1.1 定义数据库表配置参数(代码路径:examples/tcaplus/C++_common_for_tdr1.0/common.h)

主要设置表的相关配置参数,在代码文件的头部

// 目标业务的tcapdir地址
static const char DIR_URL_ARRAY[][TCAPLUS_MAX_STRING_LENGTH] =
{
   "tcp://10.191.***.99:9999",
   "tcp://10.191.***.88:9999"
};
// 目标业务的tcapdir 地址个数
static const int32_t DIR_URL_COUNT = 2;

// 目标业务的集群ID
static const int32_t APP_ID = 3;
// 目标业务的表格组ID
static const int32_t ZONE_ID = 1;
// 目标业务的业务密码
static const char * SIGNATURE = "*******";
// 目标业务的表名 PLAYERONLINECNT
static const char * TABLE_NAME = "PLAYERONLINECNT";

4.1.2 创建日志句柄(代码路径:examples/tcaplus/C++_common_for_tdr1.0/common.h)

通过日志配置文件tlogconf.xml创建日志句柄,用于SDK的日志打印

//TCaplus service 日志类
TcaplusService::TLogger* g_pstTlogger;
LPTLOGCATEGORYINST g_pstLogHandler;
LPTLOGCTX g_pstLogCtx;
int32_t InitLog()
{
    // 日志配置文件的绝对路径
    const char* sLogConfFile = "tlogconf.xml";
    // 日志类名
    const char* sCategoryName = "mytest";
    //从配置文件初始化日志句柄
    g_pstLogCtx = tlog_init_from_file(sLogConfFile);
    if (NULL == g_pstLogCtx)
    {
        fprintf(stderr, "tlog_init_from_file failed.\n");
        return -1;
    }
    // 获取日志类
    g_pstLogHandler = tlog_get_category(g_pstLogCtx, sCategoryName);
    if (NULL == g_pstLogHandler)
    {
        fprintf(stderr, "tlog_get_category(mytest) failed.\n");
        return -2;
    }
    // 初始化日志句柄
    g_pstTlogger = new TcaplusService::TLogger(g_pstLogHandler);
    if (NULL == g_pstTlogger)
    {
        fprintf(stderr, "TcaplusService::TLogger failed.\n");
        return -3;
    }
    return 0;`
}

4.1.3 创建SDK客户端(代码路径:examples/tcaplus/C++_common_for_tdr1.0/common.h)

通过该代码创建一个Tcaplus客户端,该客户端只允许单线程使用,多线程模型可以在每个线程上初始化一个客户端实例

//Tcaplus service API的客户端主类
TcaplusService::TcaplusServer g_stTcapSvr;
//表的meta信息
extern unsigned char g_szMetalib_tcaplus_tb[];
LPTDRMETA g_szTableMeta = NULL;
int32_t InitServiceAPI()
{
    // 初始化
    int32_t iRet = g_stTcapSvr.Init(g_pstTlogger, /*module_id*/0, /*app id*/APP_ID, /*zone id*/ZONE_ID, /*signature*/SIGNATURE);
    if (0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.Init failed, iRet: %d.", iRet);
        return iRet;
    }

    // 添加目录服务器
    for (int32_t i = 0; i< DIR_URL_COUNT; i++)
    {
        iRet = g_stTcapSvr.AddDirServerAddress(DIR_URL_ARRAY[i]);
        if (0 != iRet)
        {
            tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.AddDirServerAddress(%s) failed, iRet: %d.", DIR_URL_ARRAY[i], iRet);
            return iRet;
        }
    }

    // 取得表的meta描述
    g_szTableMeta = tdr_get_meta_by_name((LPTDRMETALIB)g_szMetalib_tcaplus_tb, TABLE_NAME);
    if(NULL == g_szTableMeta)
    {
        tlog_error(g_pstLogHandler, 0, 0,"tdr_get_meta_by_name(%s) failed.", TABLE_NAME);
        return -1;
    }

    // 注册数据库表(连接dir服务器,认证,获取表路由),10s超时
    iRet = g_stTcapSvr.RegistTable(TABLE_NAME, g_szTableMeta, /*timeout_ms*/10000);
    if(0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.RegistTable(%s) failed, iRet: %d.", TABLE_NAME, iRet);
        return iRet;
    }

    // 连接表对应的所有tcaplus proxy服务器
    iRet = g_stTcapSvr.ConnectAll(/*timeout_ms*/10000, 0);
    if(0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.ConnectAll failed, iRet: %d.", iRet);
        return iRet;
    }

    return 0;
}

4.1.4 发送请求(代码路径:examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count/main.cpp)

创建请求,并且添加记录,使用客户端发送请求

/*
    用户缓存,返回的res应答将携带此缓存返回
    用户缓存由用户自己定义,缓存空间最大在tcaplus_protocol_cs.xml里边已经定义
    缓存空间最大为TCAPLUS_MAX_USERBUFF_LEN=1024字节
*/
typedef struct
{
    char m_szUserMessage[128];
    int32_t m_dwMagicNo;
    //...   
}TB_USERBUFFER;

int32_t SendGetCountRequest(int32_t dwKey)
{
    // 请求对象类
    TcaplusService::TcaplusServiceRequest* pstRequest = g_stTcapSvr.GetRequest(TABLE_NAME);
    if (NULL == pstRequest)
    {
        tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.GetRequest(%s) failed.", TABLE_NAME);
        return -1;
    }
    int32_t iRet = pstRequest->Init(TCAPLUS_API_GET_TABLE_RECORD_COUNT_REQ, NULL, 0, 0, 0, 0);
    if(0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "pstRequest->Init(TCAPLUS_API_GET_TABLE_RECORD_COUNT_REQ) failed, iRet: %d.", iRet);
        return iRet;
    }
    // 发送请求消息包
    iRet = g_stTcapSvr.SendRequest(pstRequest);
    if(0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "g_stTcapSvr.SendRequest failed, iRet: %d.", iRet);
        return iRet;
    }
    return 0;
}

4.1.5 处理响应(代码路径:examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count/main.cpp)

处理响应,判断响应的返回值做响应处理,返回成功则从响应中获取记录

// 响应处理函数
int32_t HandleResponse(TB_USERBUFFER *pstUserBuffer, TcaplusServiceResponse* pstTcapRspRcved)
{
    if(NULL == pstTcapRspRcved || NULL == pstUserBuffer)
    {
        tlog_error(g_pstLogHandler, 0, 0, "NULL == pstTcapRspRcved || NULL == pstUserBuffer");
        return -1;
    }

    // 用户可以通过GetResult等接口获得返回的信息,其它接口参见头文件
    const char* pszTableName = pstTcapRspRcved->GetTableName();
    int32_t dwCmd = pstTcapRspRcved->GetCmd();
    int32_t dwResult = pstTcapRspRcved->GetResult();
    // 操作失败
    if( 0 != dwResult)
    {   
        tlog_error(g_pstLogHandler, 0, 0, "GetResult is not zero. dwResult: %d .", dwResult);
        if (TcaplusService::ERR_TIME_OUT == dwResult)
        {
            HandleTimeoutResponse(pstUserBuffer->m_dwMagicNo);
        }
        return -2;
    }    
    // 处理response包, 这里需要根据响应包做分包处理逻辑
    return HandleGetCount(pstTcapRspRcved);
}

// 超时处理函数
int32_t HandleTimeoutResponse(int32_t dwKey)
{
    tlog_error(g_pstLogHandler, 0, 0, "The TimeStamp = [%d] GameSvrID = [mysvrid], response is timeout. ", dwKey);
    //处理超时的其它逻辑
    return 0;
}

int32_t HandleGetCount(TcaplusServiceResponse* pstTcapRspRcved)
{
    if(NULL == pstTcapRspRcved)
    {
        return -1;
    }

    int64_t table_record_count = 0;

    // 获取单个表的记录总数
    int32_t iRet = pstTcapRspRcved->GetTableRecordCount(table_record_count);
    if(0 != iRet)
    {
        printf( "GetTableRecordCount failed, iRet: %d \n", iRet);
        tlog_error(g_pstLogHandler, 0, 0, "GetTableRecordCount failed, iRet: %d.", iRet);
        return -2;
    }
    printf("table_record_count: %lld\n", table_record_count);
    return 0;
}

4.1.6 销毁客户端(代码路径:examples/tcaplus/C++_common_for_tdr1.0/common.h)

进程退出时,先调用客户端的Fini函数,销毁客户端,再销毁日志句柄,防止客户端退出时打日志coredump

void  Finish()
{
    //先销毁客户端
    g_stTcapSvr.Fini();
    tlog_info(g_pstLogHandler, 0, 0, "mytest finish!");
    //再销毁日志句柄
    delete g_pstTlogger;
    g_pstTlogger = NULL;
    tlog_fini_ctx(&g_pstLogCtx);
}

4.1.7 example异步框架(代码路径:examples/tcaplus/C++_common_for_tdr1.0/common.h)

通过实现回调函数的三个函数指针,proc主框架会调用相应的函数发送请求,接收响应和超时处理,该框架所有例子通用

// 函数回调机制
struct tagCallbackFunctions
{
    // 发送请求
    int (*pfnSendRequest)(int32_t key);
    // 接收响应
    int (*pfnHandResponse)(TB_USERBUFFER* pstUserBuffer, TcaplusServiceResponse* pstTcapRspRcved);
    // 超时处理
    int (*pfnHandTimeOutResponse)(int32_t dwKey);
};
typedef struct tagCallbackFunctions CALLBACK_FUNCTIONS;
CALLBACK_FUNCTIONS g_stCallbackFunctions;

int32_t main(int32_t argc, char *argv[])
{
    int32_t iRet = 0;
    // 初始化
    iRet = Init();
    if (0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "Init failed, iRet: %d, please check the mytest.log for detail. ", iRet);
        return iRet;
    }    

    // 下面设置回调函数
    g_stCallbackFunctions.pfnSendRequest = SendGetCountRequest;
    g_stCallbackFunctions.pfnHandResponse =  HandleResponse;
    g_stCallbackFunctions.pfnHandTimeOutResponse = HandleTimeoutResponse;

    // 处理逻辑
    iRet = Proc();
    if (0 != iRet)
    {
        tlog_error(g_pstLogHandler, 0, 0, "Mytest failed, iRet: %d, please check the mytest.log for detail. ", iRet);
    }  

    // 清理环境
    Finish();
    return iRet;
}

int32_t Init()
{
    int32_t iRet = 0;
    // 初始化日志
    iRet = InitLog();
    if (0 != iRet)
    {
        return iRet;
    }
    // 初始化客户端
    iRet = InitServiceAPI();
    if (0 != iRet)
    {
        return iRet;
    }
    return iRet;
}

#define TIME_MS_PER_SEC 1000
static int TOTAL_SEND_RECV_NUM = 5; // 循环发送请求的次数,可以更改此值来发送多个请求
#define TCAPLUS_TIMEOUT_MS 10 * TIME_MS_PER_SEC // 发送req以后接收res包的超时时间,单位毫秒
#define TCAPLUS_SLEEEP_US 1000 // Proc一轮循环等待时间,单位微秒
std::map<int32_t,struct timeval> g_mapKeyTime; // Map二元组分别为发送数据的Key和发送时间
// 异步主处理框架
int32_t Proc()
{
    int32_t iRet = 0;
    // 本例子中使用变量iSendRequestCount是控制发包的数量
    int32_t iSendRequestCount = 0;
    while(true)
    {
        int32_t iKey = iSendRequestCount;
        // 异步发送多个请求
        if (iSendRequestCount < TOTAL_SEND_RECV_NUM)
        {
            if(NULL == g_stCallbackFunctions.pfnSendRequest)
            {
                tlog_error(g_pstLogHandler, 0, 0, "g_stCallbackFunctions.pfnSendRequest is NULL, so will finish example.");
                break;
            }
            iRet = (*g_stCallbackFunctions.pfnSendRequest)(iKey);
            if (0 != iRet)
            {
                tlog_error(g_pstLogHandler, 0, 0, "SendRequest failed, iRet: %d .", iRet);
                break;
            }
            // 添加时间戳,用于超时判断
            struct timeval now = {0};
            gettimeofday(&now, NULL);
            g_mapKeyTime[iKey] = now;
            iSendRequestCount++;
        }

        // 接收并处理包
        iRet = RecvPackage();
        if (0 != iRet)
        {
            tlog_error(g_pstLogHandler, 0, 0, "RecvPackage failed, iRet: %d .", iRet);
            break;
        }    

        // 判断发送的请求对应的回包是否有超时的现象。如果接收到响应包的时间超过了TIME_MS_PER_SEC,表示超时,将此key从g_mapKeyTime删除掉。
        struct timeval now = { 0 };
        gettimeofday(&now, NULL);
        std::map<int32_t, struct timeval>::iterator iter;
        for (iter = g_mapKeyTime.begin(); iter != g_mapKeyTime.end();)
        {
            int32_t iTotalUseTime = now.tv_sec * TIME_MS_PER_SEC + now.tv_usec / TIME_MS_PER_SEC - iter->second.tv_sec * TIME_MS_PER_SEC - iter->second.tv_usec / TIME_MS_PER_SEC;
            if (iTotalUseTime > TCAPLUS_TIMEOUT_MS)
            {
                tlog_debug(g_pstLogHandler, 0, 0, "RecvPackage timeout, key is %d iTotalUseTime = %d ", iter->first, iTotalUseTime);
                // 超时处理
                (*g_stCallbackFunctions.pfnHandTimeOutResponse)(iter->first);
                g_mapKeyTime.erase(iter++);
            }
            else
            {
                iter++;
            }
        }

        // 如果所有的包发送完毕,并且所有的包都处理完毕,则跳出循环,结束Proc()
        if (iSendRequestCount == TOTAL_SEND_RECV_NUM && 0 == g_mapKeyTime.size())
        {
            tlog_info(g_pstLogHandler, 0, 0, "All response is handle , so this test will finish. for more detail to read mytest.log!");
            break;
        }

        // 其它的工作...
        usleep(TCAPLUS_SLEEEP_US);
    }
    return iRet;
}

#define MAX_HANDLE_COUNTER_ONBUSY 1000
// 接收响应包
int32_t RecvPackage()
{
    uint32_t    dwHandleCnter = 0;
    uint32_t    dwMaxHandleCnter = MAX_HANDLE_COUNTER_ONBUSY;

    // 当本次循环次数达到MAX_HANDLE_COUNTER_ONBUSY仍然没有接收到TOTAL_SEND_RECV_NUM的响应包时,退出接收包的循环。
    // 尽量循环多次收包,防止本地积攒过多回包没有处理使Api本地缓存过满,从而proxy无法发包给Api,导致丢包。
    while (dwHandleCnter++ < dwMaxHandleCnter)
    {
        // warning 该函数返回的response指针是全局共用的,所以使用后请勿手动调用response->Destruct或者delete response。
        TcaplusServiceResponse* pstTcapRspRcved = NULL;

        // 接收响应包
        int32_t iRet = g_stTcapSvr.RecvResponse(pstTcapRspRcved);
        if (iRet < 0)
        {
            //失败,打印对应的错误码。
            tlog_debug(g_pstLogHandler, 0, 0, "g_stTcapSvr.RecvResponse failed, iRet: %d.", iRet);
        }
        else
        {
            // iRet == 1 成功,收到1个完整的响应包,此时输出参数response为非NULL指针。
            if(1 == iRet && NULL != pstTcapRspRcved)
            {
                TB_USERBUFFER *pstUserBuffer = NULL;
                const char  *sTmpUserBuffer = NULL;
                size_t      iTmpUserBuffSize;
                // 提取用户自定义数据,如果发包设置了UserBuff,则回包必须有UserBuff,否则出错
                sTmpUserBuffer = pstTcapRspRcved->GetUserBuff(&iTmpUserBuffSize);
                if(NULL == sTmpUserBuffer)
                {
                    tlog_error(g_pstLogHandler, 0, 0, "GetUserBuff failed ! ");
                    continue;
                }
                pstUserBuffer = (TB_USERBUFFER*)sTmpUserBuffer;
                if(NULL == pstUserBuffer)
                {
                    tlog_error(g_pstLogHandler, 0, 0, "GetUserBuff failed ! ");
                    continue;
                }

                // 从map中查找是否有key对应的记录,如果有,表示当前key的请求没有超时;没有表示超时。分别进入不同的逻辑。
                int32_t dwKey = pstUserBuffer->m_dwMagicNo;
                std::map<int32_t, struct timeval>::iterator it;
                it = g_mapKeyTime.find(dwKey);
                if (it != g_mapKeyTime.end())
                {
                    if(NULL == g_stCallbackFunctions.pfnHandResponse)
                    {
                        tlog_error(g_pstLogHandler, 0, 0, "g_stCallbackFunctions.pfnHandResponse is NULL, so will skip this package.");
                    }
                    else
                    {
                        // 对于多记录返回的,会有分包返回的场景
                        if (1 != pstTcapRspRcved->HaveMoreResPkgs())
                        {
                            // 删除Map对应Key的记录
                            g_mapKeyTime.erase(dwKey);
                        }
                        // 处理回包
                        (*g_stCallbackFunctions.pfnHandResponse)(pstUserBuffer, pstTcapRspRcved);
                    }
                }
                else
                {
                    if(NULL == g_stCallbackFunctions.pfnHandTimeOutResponse)
                    {
                        tlog_error(g_pstLogHandler, 0, 0, "g_stCallbackFunctions.pfnHandTimeOutResponse is NULL, so will skip this package.");
                    }
                    else
                    {
                        tlog_error(g_pstLogHandler, 0, 0, "This Key = [%d] is time out, so don't handle it!", dwKey);
                    }
                }
            }
            else
            {
                // iRet == 0 成功,但未收到完整的响应包。
                tlog_debug(g_pstLogHandler, 0, 0, "this loop not recieve a complete package!");
                break;
            }
        }
    }
    return 0;
}

5. 常见问题

详见错误码含义和处理方法

6. 其它参考文档

[TDR表][Go SDK]查询表记录总数接口说明

results matching ""

    No results matching ""