[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时会被用到:
- 目录服务器地址列表
- 业务ID
- 业务访问密码
- 游戏区ID
- 数据表名
4. 示例代码
4.1 异步调用示例代码
示例代码的基本执行过程:
- 定义表配置参数
- 创建日志句柄;
- 创建客户端;
- 发送请求;
- 处理响应;
- 销毁客户端。
- 异步框架;
第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. 常见问题
详见错误码含义和处理方法。