[TDR Table][C++ SDK] Get the Total Number of Table Records
1. Interface Description
Get the total number of table records (example path: examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count)
2. Version Requirements
This interface is provided in all versions without special requirements.
3. Preparations
Refer to Preparation document to complete the preparation before using this interface, create the following TDR Generic table, and use the TDR tool to convert the XML table into C++ code. LAYERONLINECNT table description file 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="in minute" />
<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="online count of ios" />
<entry name="OnlineCntAndroid" type="uint32" defaultvalue="0" desc="online count of android" />
<entry name="BinaryLen" type="smalluint" defaultvalue="1" desc="data length; ignore source checking when the length is 0"/>
<entry name="binary" type="tinyint" desc="binary" count= "1000" refer="BinaryLen" />
<entry name="binary2" type="tinyint" desc="binary2" count= "1000" refer="BinaryLen" />
<entry name="strstr" type="string" size="64" desc="string"/>
<index name="index_id" column="TimeStamp"/>
</struct>
</metalib>
Get the following information after the preparation. These details will be used by the SDK:
- Directory server address list
- App ID
- App access password
- Game zone ID
- Table name
4. Example Code
4.1 Example Code for Asynchronous Call
Basic execution process of example code:
- Define table configuration parameters
- Create a log handle;
- Create a client;
- Send a request;
- Process the response;
- Destroy the client.
- Asynchronous framework;
Steps 1,2,3,6,7 are common codes for all examples. Focus on steps 4 and 5, which are sending a request and processing the response
4.1.1 Define table configuration parameters (code path: examples/tcaplus/C++_common_for_tdr1.0/common.h)
It mainly sets the configuration parameters of the table, which are located in the header of the code file
// Tcapdir address of the target app
static const char DIR_URL_ARRAY[][TCAPLUS_MAX_STRING_LENGTH] =
{
"tcp://10.191.***.99:9999",
"tcp://10.191.***.88:9999"
};
// Number of tcapdir addresses of the target app
static const int32_t DIR_URL_COUNT = 2;
// Set ID of the target app
static const int32_t APP_ID = 3;
// Table group ID of the target app
static const int32_t ZONE_ID = 1;
// App password of the target app
static const char * SIGNATURE = "*******";
// Table name of the target app PLAYERONLINECNT
static const char * TABLE_NAME = "PLAYERONLINECNT";
4.1.2 Create a log handle (code path: examples/tcaplus/C++_common_for_tdr1.0/common.h)
Create a log handle through the log configuration file tlogconf.xml for SDK log printing
//Tcaplus service log class
TcaplusService::TLogger* g_pstTlogger;
LPTLOGCATEGORYINST g_pstLogHandler;
LPTLOGCTX g_pstLogCtx;
int32_t InitLog()
{
// Absolute path to the log configuration file
const char* sLogConfFile = "tlogconf.xml";
// Log class name
const char* sCategoryName = "mytest";
//Use the configuration file to initialize a log handle
g_pstLogCtx = tlog_init_from_file(sLogConfFile);
if (NULL == g_pstLogCtx)
{
fprintf(stderr, "tlog_init_from_file failed.\n");
return -1;
}
// Get the log class
g_pstLogHandler = tlog_get_category(g_pstLogCtx, sCategoryName);
if (NULL == g_pstLogHandler)
{
fprintf(stderr, "tlog_get_category(mytest) failed.\n");
return -2;
}
// Initialize the log handle
g_pstTlogger = new TcaplusService::TLogger(g_pstLogHandler);
if (NULL == g_pstTlogger)
{
fprintf(stderr, "TcaplusService::TLogger failed.\n");
return -3;
}
return 0;`
}
4.1.3 Create a SDK client (code path: examples/tcaplus/C++_common_for_tdr1.0/common.h)
Create a Tcaplus client through this code. This client can only be used by a single thread. The multithreading model can initialize a client instance on each thread
//Main client class of the Tcaplus service API
TcaplusService::TcaplusServer g_stTcapSvr;
//Meta information of the table
extern unsigned char g_szMetalib_tcaplus_tb[];
LPTDRMETA g_szTableMeta = NULL;
int32_t InitServiceAPI()
{
// Initialization
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;
}
// Add a directory server
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;
}
}
// Get the meta description of the table
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;
}
// Register a database table with a 10-second timeout (connect to the dir server, authenticate, and obtain table paths).
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;
}
// Connect to all tcaplus proxy servers corresponding to the table
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 Send a request (code path: examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count/main.cpp)
Create a request, add a record, and send a request by the client
/*
User buffer, which is carried back by the res
User buffer is defined by the user, and the maximum buffer space has been defined in tcaplus_protocol_cs.xml
Maximum buffer space is TCAPLUS_MAX_USERBUFF_LEN=1024 bytes
*/
typedef struct
{
char m_szUserMessage[128];
int32_t m_dwMagicNo;
//...
}TB_USERBUFFER;
int32_t SendGetCountRequest(int32_t dwKey)
{
// Request object class
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;
}
// Send a request message package
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 Process the response (code path: examples/tcaplus/C++_tdr1.0_asyncmode_generic_simpletable/SingleOperation/count/main.cpp)
Process the response, judge the return value of the response for response processing, and obtain records from the response if it is returned successfully
// Response processing function
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;
}
// Users can get the returned information through interfaces such as GetResult. For other interfaces, see the header file
const char* pszTableName = pstTcapRspRcved->GetTableName();
int32_t dwCmd = pstTcapRspRcved->GetCmd();
int32_t dwResult = pstTcapRspRcved->GetResult();
// Operation failure
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;
}
// Process the response package, and we need to split the package according to the response package
return HandleGetCount(pstTcapRspRcved);
}
// Timeout processing function
int32_t HandleTimeoutResponse(int32_t dwKey)
{
tlog_error(g_pstLogHandler, 0, 0, "The TimeStamp = [%d] GameSvrID = [mysvrid], response is timeout. ", dwKey);
//Other logics for processing timeout
return 0;
}
int32_t HandleGetCount(TcaplusServiceResponse* pstTcapRspRcved)
{
if(NULL == pstTcapRspRcved)
{
return -1;
}
int64_t table_record_count = 0;
// Get the total number of records of a single table
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 Destroy the client (code path: examples/tcaplus/C++_common_for_tdr1.0/common.h)
When the process exits, first call the Fini function of the client, destroy the client, and then destroy the log handle to prevent the log coredump when the client exits
void Finish()
{
//Destroy the client first
g_stTcapSvr.Fini();
tlog_info(g_pstLogHandler, 0, 0, "mytest finish!");
//Then destroy the log handle
delete g_pstTlogger;
g_pstTlogger = NULL;
tlog_fini_ctx(&g_pstLogCtx);
}
4.1.7 Example asynchronous framework (code path: examples/tcaplus/C++_common_for_tdr1.0/common.h)
The proc main framework calls the corresponding functions to send requests, receive responses, and handle timeouts by implementing the three function pointers of the callback function. All examples of this framework are common
// Function callback mechanism
struct tagCallbackFunctions
{
// Send a request
int (*pfnSendRequest)(int32_t key);
// Receive the response
int (*pfnHandResponse)(TB_USERBUFFER* pstUserBuffer, TcaplusServiceResponse* pstTcapRspRcved);
// Timeout processing
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;
// Initialization
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;
}
// Now set the callback function
g_stCallbackFunctions.pfnSendRequest = SendGetCountRequest;
g_stCallbackFunctions.pfnHandResponse = HandleResponse;
g_stCallbackFunctions.pfnHandTimeOutResponse = HandleTimeoutResponse;
// Processing logics
iRet = Proc();
if (0 != iRet)
{
tlog_error(g_pstLogHandler, 0, 0, "Mytest failed, iRet: %d, please check the mytest.log for detail. ", iRet);
}
// Clean up the environment
Finish();
return iRet;
}
int32_t Init()
{
int32_t iRet = 0;
// Initialize the log
iRet = InitLog();
if (0 != iRet)
{
return iRet;
}
// Initialize the client
iRet = InitServiceAPI();
if (0 != iRet)
{
return iRet;
}
return iRet;
}
#define TIME_MS_PER_SEC 1000
static int TOTAL_SEND_RECV_NUM = 5; // Number of loops to send requests, which can be changed to send multiple requests.
#define TCAPLUS_TIMEOUT_MS 10 * TIME_MS_PER_SEC // Timeout of receiving a res package after sending a req, in milliseconds
#define TCAPLUS_SLEEEP_US 1000 // Waiting time of a proc loop, in microseconds
std::map<int32_t,struct timeval> g_mapKeyTime; // The Map binary is the Key and time of sending data
// Asynchronous main processing framework
int32_t Proc()
{
int32_t iRet = 0;
// The variable iSendRequestCount used in this sample is to control the number of packages
int32_t iSendRequestCount = 0;
while(true)
{
int32_t iKey = iSendRequestCount;
// Send multiple requests asynchronously
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;
}
// Add a timestamp for timeout judgment
struct timeval now = {0};
gettimeofday(&now, NULL);
g_mapKeyTime[iKey] = now;
iSendRequestCount++;
}
// Receive and process packages
iRet = RecvPackage();
if (0 != iRet)
{
tlog_error(g_pstLogHandler, 0, 0, "RecvPackage failed, iRet: %d .", iRet);
break;
}
// Judge whether the returned packages corresponding to the sent requests have timed out. If the time for receiving a response package is longer than TIME_MS_PER_SEC, it indicates a timeout. Remove this key from 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);
// Timeout processing
(*g_stCallbackFunctions.pfnHandTimeOutResponse)(iter->first);
g_mapKeyTime.erase(iter++);
}
else
{
iter++;
}
}
// If all packages have been sent and processed, break out of the loop and end 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;
}
// Other tasks...
usleep(TCAPLUS_SLEEEP_US);
}
return iRet;
}
#define MAX_HANDLE_COUNTER_ONBUSY 1000
// Receive response packages
int32_t RecvPackage()
{
uint32_t dwHandleCnter = 0;
uint32_t dwMaxHandleCnter = MAX_HANDLE_COUNTER_ONBUSY;
// The loop of receiving packages will be terminated when the number of loop times reaches MAX_HANDLE_COUNTER_ONBUSY and the response package TOTAL_SEND_RECV_NUM is still not received.
// Loop as many times as possible to prevent the Api local cache from becoming too full due to the accumulation of too many packages in the local area without processing, resulting in the proxy being unable to send the packages to Api and package loss.
while (dwHandleCnter++ < dwMaxHandleCnter)
{
// The response pointer returned by this warning function is globally shared, so do not call response -> Destruct or delete response manually after use.
TcaplusServiceResponse* pstTcapRspRcved = NULL;
// Receive response packages
int32_t iRet = g_stTcapSvr.RecvResponse(pstTcapRspRcved);
if (iRet < 0)
{
//If it failed, print the corresponding error code.
tlog_debug(g_pstLogHandler, 0, 0, "g_stTcapSvr.RecvResponse failed, iRet: %d.", iRet);
}
else
{
// If iRet == 1 is successful, it means a complete response package is received, and the output parameter response is a non-NULL pointer.
if(1 == iRet && NULL != pstTcapRspRcved)
{
TB_USERBUFFER *pstUserBuffer = NULL;
const char *sTmpUserBuffer = NULL;
size_t iTmpUserBuffSize;
// Get user-defined data. If UserBuff is set for the request package, the response package must have UserBuff; otherwise, an error will occur.
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;
}
// Find whether there is a record corresponding to the key on the map. If yes, it indicates that the current key request has not timed out; otherwise, it means timeout. Enter different logic respectively.
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
{
// For multiple records returned, there will be package splitting return scenarios
if (1 != pstTcapRspRcved->HaveMoreResPkgs())
{
// Delete the record of the key corresponding to the Map
g_mapKeyTime.erase(dwKey);
}
// Process the response package
(*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
{
// If IRet == 0 is successful, it means a complete response package is not received.
tlog_debug(g_pstLogHandler, 0, 0, "this loop not recieve a complete package!");
break;
}
}
}
return 0;
}
5. FAQ
For details, see Meaning and Handling of Error Codes.
6. Other Reference Documents
[TDR Table] [Go SDK] Interface Description for Counting the Total Number of Table Records