493 lines
11 KiB
C++
493 lines
11 KiB
C++
#include "stdafx.h"
|
|
#include "Config.h"
|
|
#include "Peer.h"
|
|
#include "DBManager.h"
|
|
#include "ClientManager.h"
|
|
#include "GuildManager.h"
|
|
#include "ItemAwardManager.h"
|
|
#include "HB.h"
|
|
#include "PrivManager.h"
|
|
#include "MoneyLog.h"
|
|
#include "Marriage.h"
|
|
#include "ItemIDRangeManager.h"
|
|
#include <signal.h>
|
|
#undef OS_FREEBSD
|
|
void SetPlayerDBName(const char* c_pszPlayerDBName);
|
|
void SetTablePostfix(const char* c_pszTablePostfix);
|
|
int Start();
|
|
|
|
std::string g_stTablePostfix;
|
|
std::string g_stLocaleNameColumn = "name";
|
|
std::string g_stLocale = "euckr";
|
|
std::string g_stPlayerDBName = "";
|
|
|
|
|
|
bool g_bHotBackup = false;
|
|
BOOL g_test_server = false;
|
|
|
|
//단위 초
|
|
int g_iPlayerCacheFlushSeconds = 60*7;
|
|
int g_iItemCacheFlushSeconds = 60*5;
|
|
|
|
//g_iLogoutSeconds 수치는 g_iPlayerCacheFlushSeconds 와 g_iItemCacheFlushSeconds 보다 길어야 한다.
|
|
int g_iLogoutSeconds = 60*10;
|
|
|
|
int g_log = 1;
|
|
|
|
|
|
// MYSHOP_PRICE_LIST
|
|
int g_iItemPriceListTableCacheFlushSeconds = 540;
|
|
// END_OF_MYSHOP_PRICE_LIST
|
|
|
|
#ifdef OS_FREEBSD
|
|
extern const char * _malloc_options;
|
|
#endif
|
|
|
|
extern void WriteVersion();
|
|
|
|
namespace
|
|
{
|
|
struct SQLConnectionConfig
|
|
{
|
|
char host[64];
|
|
char database[64];
|
|
char user[64];
|
|
char password[64];
|
|
int port;
|
|
};
|
|
|
|
const char* BoolState(bool value)
|
|
{
|
|
return value ? "on" : "off";
|
|
}
|
|
|
|
const char* EmptyToLabel(const std::string& value, const char* fallback)
|
|
{
|
|
return value.empty() ? fallback : value.c_str();
|
|
}
|
|
|
|
bool CopyEnvString(const char* env_name, char* dest, size_t dest_size)
|
|
{
|
|
const char* value = std::getenv(env_name);
|
|
if (!value)
|
|
return false;
|
|
|
|
strlcpy(dest, value, dest_size);
|
|
return true;
|
|
}
|
|
|
|
bool CopyEnvInt(const char* env_name, int* dest)
|
|
{
|
|
const char* value = std::getenv(env_name);
|
|
if (!value)
|
|
return false;
|
|
|
|
str_to_number(*dest, value);
|
|
return true;
|
|
}
|
|
|
|
bool HasSQLConfig(const SQLConnectionConfig& config)
|
|
{
|
|
return config.host[0] && config.database[0] && config.user[0] && config.password[0];
|
|
}
|
|
|
|
bool ParseSQLConfig(const char* value, SQLConnectionConfig* config, const char* label)
|
|
{
|
|
int token_count = sscanf(
|
|
value,
|
|
" %63s %63s %63s %63s %d ",
|
|
config->host,
|
|
config->database,
|
|
config->user,
|
|
config->password,
|
|
&config->port);
|
|
|
|
if (token_count < 4 || !HasSQLConfig(*config))
|
|
{
|
|
fprintf(stderr, "%s syntax: <host db user password [port]>\n", label);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LoadSQLConfig(const char* key, const char* env_prefix, SQLConnectionConfig* config)
|
|
{
|
|
char line[256 + 1];
|
|
bool loaded_from_file = false;
|
|
|
|
if (CConfig::instance().GetValue(key, line, sizeof(line) - 1))
|
|
{
|
|
if (!ParseSQLConfig(line, config, key))
|
|
return false;
|
|
|
|
loaded_from_file = true;
|
|
}
|
|
|
|
bool overridden = false;
|
|
const std::string prefix = env_prefix;
|
|
overridden |= CopyEnvString((prefix + "_HOST").c_str(), config->host, sizeof(config->host));
|
|
overridden |= CopyEnvString((prefix + "_DB").c_str(), config->database, sizeof(config->database));
|
|
overridden |= CopyEnvString((prefix + "_USER").c_str(), config->user, sizeof(config->user));
|
|
overridden |= CopyEnvString((prefix + "_PASSWORD").c_str(), config->password, sizeof(config->password));
|
|
overridden |= CopyEnvInt((prefix + "_PORT").c_str(), &config->port);
|
|
|
|
if (overridden)
|
|
sys_log(0, "CONFIG: %s overridden from environment", key);
|
|
|
|
if (!loaded_from_file && !overridden)
|
|
{
|
|
sys_err("%s not configured", key);
|
|
return false;
|
|
}
|
|
|
|
if (!HasSQLConfig(*config))
|
|
{
|
|
sys_err("%s incomplete after applying config/environment overrides", key);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ConnectDatabase(int slot, const SQLConnectionConfig& config, const char* label)
|
|
{
|
|
sys_log(0, "connecting to MySQL server (%s)", label);
|
|
|
|
int retry_count = 5;
|
|
do
|
|
{
|
|
if (CDBManager::instance().Connect(slot, config.host, config.port, config.database, config.user, config.password))
|
|
{
|
|
sys_log(0, " OK");
|
|
return true;
|
|
}
|
|
|
|
sys_log(0, " failed, retrying in 5 seconds");
|
|
fprintf(stderr, " failed, retrying in 5 seconds");
|
|
sleep(5);
|
|
}
|
|
while (retry_count--);
|
|
|
|
return false;
|
|
}
|
|
|
|
void LogStartupSummary(int heart_fps, int player_id_start)
|
|
{
|
|
sys_log(0,
|
|
"[STARTUP] locale=%s table_postfix=%s player_db=%s player_id_start=%d heart_fps=%d test_server=%s log=%s hotbackup=%s",
|
|
EmptyToLabel(g_stLocale, "<unset>"),
|
|
EmptyToLabel(g_stTablePostfix, "<none>"),
|
|
EmptyToLabel(g_stPlayerDBName, "<unset>"),
|
|
player_id_start,
|
|
heart_fps,
|
|
BoolState(g_test_server),
|
|
BoolState(g_log != 0),
|
|
BoolState(g_bHotBackup)
|
|
);
|
|
|
|
sys_log(0,
|
|
"[STARTUP] cache_flush player=%d item=%d pricelist=%d logout=%d locale_name_column=%s",
|
|
g_iPlayerCacheFlushSeconds,
|
|
g_iItemCacheFlushSeconds,
|
|
g_iItemPriceListTableCacheFlushSeconds,
|
|
g_iLogoutSeconds,
|
|
EmptyToLabel(g_stLocaleNameColumn, "<unset>")
|
|
);
|
|
}
|
|
}
|
|
|
|
void emergency_sig(int sig)
|
|
{
|
|
if (sig == SIGSEGV)
|
|
sys_log(0, "SIGNAL: SIGSEGV");
|
|
else if (sig == SIGUSR1)
|
|
sys_log(0, "SIGNAL: SIGUSR1");
|
|
|
|
if (sig == SIGSEGV)
|
|
abort();
|
|
}
|
|
|
|
int main()
|
|
{
|
|
log_init();
|
|
|
|
WriteVersion();
|
|
|
|
#ifdef OS_FREEBSD
|
|
_malloc_options = "A";
|
|
#endif
|
|
|
|
CConfig Config;
|
|
CNetPoller poller;
|
|
CDBManager DBManager;
|
|
CClientManager ClientManager;
|
|
PlayerHB player_hb;
|
|
CGuildManager GuildManager;
|
|
CPrivManager PrivManager;
|
|
CMoneyLog MoneyLog;
|
|
ItemAwardManager ItemAwardManager;
|
|
marriage::CManager MarriageManager;
|
|
CItemIDRangeManager ItemIDRangeManager;
|
|
|
|
if (!Start())
|
|
return 1;
|
|
|
|
GuildManager.Initialize();
|
|
MarriageManager.Initialize();
|
|
ItemIDRangeManager.Build();
|
|
|
|
sys_log(0, "Metin2DBCacheServer Start\n");
|
|
|
|
CClientManager::instance().MainLoop();
|
|
|
|
signal_timer_disable();
|
|
|
|
sys_log(0, "[SHUTDOWN] DB main loop finished, quitting SQL workers");
|
|
DBManager.Quit();
|
|
sys_log(0, "[SHUTDOWN] DB SQL workers stopped");
|
|
int iCount;
|
|
|
|
while (1)
|
|
{
|
|
iCount = 0;
|
|
|
|
iCount += CDBManager::instance().CountReturnQuery(SQL_PLAYER);
|
|
iCount += CDBManager::instance().CountAsyncQuery(SQL_PLAYER);
|
|
|
|
if (iCount == 0)
|
|
break;
|
|
|
|
usleep(1000);
|
|
sys_log(0, "WAITING_QUERY_COUNT %d", iCount);
|
|
}
|
|
|
|
sys_log(0, "[SHUTDOWN] DB process exiting cleanly");
|
|
|
|
log_destroy();
|
|
return 0;
|
|
}
|
|
|
|
void emptybeat(LPHEART heart, int pulse)
|
|
{
|
|
if (!(pulse % heart->passes_per_sec)) // 1초에 한번
|
|
{
|
|
}
|
|
}
|
|
|
|
//
|
|
// @version 05/06/13 Bang2ni - 아이템 가격정보 캐시 flush timeout 설정 추가.
|
|
//
|
|
int Start()
|
|
{
|
|
if (!CConfig::instance().LoadFile("conf/db.txt"))
|
|
{
|
|
fprintf(stderr, "Loading conf/db.txt failed.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!CConfig::instance().GetValue("TEST_SERVER", &g_test_server))
|
|
{
|
|
fprintf(stderr, "Real Server\n");
|
|
}
|
|
else
|
|
fprintf(stderr, "Test Server\n");
|
|
|
|
if (!CConfig::instance().GetValue("LOG", &g_log))
|
|
{
|
|
fprintf(stderr, "Log Off");
|
|
g_log= 0;
|
|
}
|
|
else
|
|
{
|
|
g_log = 1;
|
|
fprintf(stderr, "Log On");
|
|
}
|
|
|
|
|
|
int tmpValue;
|
|
|
|
int heart_beat = 50;
|
|
if (!CConfig::instance().GetValue("CLIENT_HEART_FPS", &heart_beat))
|
|
{
|
|
fprintf(stderr, "Cannot find CLIENT_HEART_FPS configuration.\n");
|
|
return false;
|
|
}
|
|
|
|
thecore_init(heart_beat, emptybeat);
|
|
signal_timer_enable(60);
|
|
|
|
char szBuf[256+1];
|
|
|
|
if (CConfig::instance().GetValue("LOCALE", szBuf, 256))
|
|
{
|
|
g_stLocale = szBuf;
|
|
sys_log(0, "LOCALE set to %s", g_stLocale.c_str());
|
|
|
|
// CHINA_DISABLE_HOTBACKUP
|
|
if ("gb2312" == g_stLocale)
|
|
{
|
|
sys_log(0, "CIBN_LOCALE: DISABLE_HOTBACKUP");
|
|
g_bHotBackup = false;
|
|
}
|
|
// END_OF_CHINA_DISABLE_HOTBACKUP
|
|
}
|
|
|
|
int iDisableHotBackup;
|
|
if (CConfig::instance().GetValue("DISABLE_HOTBACKUP", &iDisableHotBackup))
|
|
{
|
|
if (iDisableHotBackup)
|
|
{
|
|
sys_log(0, "CONFIG: DISABLE_HOTBACKUP");
|
|
g_bHotBackup = false;
|
|
}
|
|
}
|
|
|
|
|
|
if (!CConfig::instance().GetValue("TABLE_POSTFIX", szBuf, 256))
|
|
{
|
|
sys_log(0, "CONFIG: TABLE_POSTFIX not configured, using default table names");
|
|
szBuf[0] = '\0';
|
|
}
|
|
|
|
SetTablePostfix(szBuf);
|
|
|
|
if (CConfig::instance().GetValue("PLAYER_CACHE_FLUSH_SECONDS", szBuf, 256))
|
|
{
|
|
str_to_number(g_iPlayerCacheFlushSeconds, szBuf);
|
|
sys_log(0, "PLAYER_CACHE_FLUSH_SECONDS: %d", g_iPlayerCacheFlushSeconds);
|
|
}
|
|
|
|
if (CConfig::instance().GetValue("ITEM_CACHE_FLUSH_SECONDS", szBuf, 256))
|
|
{
|
|
str_to_number(g_iItemCacheFlushSeconds, szBuf);
|
|
sys_log(0, "ITEM_CACHE_FLUSH_SECONDS: %d", g_iItemCacheFlushSeconds);
|
|
}
|
|
|
|
// MYSHOP_PRICE_LIST
|
|
if (CConfig::instance().GetValue("ITEM_PRICELIST_CACHE_FLUSH_SECONDS", szBuf, 256))
|
|
{
|
|
str_to_number(g_iItemPriceListTableCacheFlushSeconds, szBuf);
|
|
sys_log(0, "ITEM_PRICELIST_CACHE_FLUSH_SECONDS: %d", g_iItemPriceListTableCacheFlushSeconds);
|
|
}
|
|
// END_OF_MYSHOP_PRICE_LIST
|
|
//
|
|
if (CConfig::instance().GetValue("CACHE_FLUSH_LIMIT_PER_SECOND", szBuf, 256))
|
|
{
|
|
DWORD dwVal = 0; str_to_number(dwVal, szBuf);
|
|
CClientManager::instance().SetCacheFlushCountLimit(dwVal);
|
|
}
|
|
|
|
int iIDStart;
|
|
if (!CConfig::instance().GetValue("PLAYER_ID_START", &iIDStart))
|
|
{
|
|
sys_err("PLAYER_ID_START not configured");
|
|
return false;
|
|
}
|
|
|
|
CClientManager::instance().SetPlayerIDStart(iIDStart);
|
|
|
|
if (CConfig::instance().GetValue("NAME_COLUMN", szBuf, 256))
|
|
{
|
|
fprintf(stderr, "%s %s", g_stLocaleNameColumn.c_str(), szBuf);
|
|
g_stLocaleNameColumn = szBuf;
|
|
}
|
|
|
|
SQLConnectionConfig player_sql = {};
|
|
SQLConnectionConfig account_sql = {};
|
|
SQLConnectionConfig common_sql = {};
|
|
SQLConnectionConfig hotbackup_sql = {};
|
|
|
|
if (!LoadSQLConfig("SQL_PLAYER", "METIN2_PLAYER_SQL", &player_sql))
|
|
return false;
|
|
|
|
if (!ConnectDatabase(SQL_PLAYER, player_sql, "player"))
|
|
return false;
|
|
|
|
fprintf(stderr, "Success PLAYER\n");
|
|
SetPlayerDBName(player_sql.database);
|
|
|
|
if (!LoadSQLConfig("SQL_ACCOUNT", "METIN2_ACCOUNT_SQL", &account_sql))
|
|
return false;
|
|
|
|
if (!ConnectDatabase(SQL_ACCOUNT, account_sql, "account"))
|
|
return false;
|
|
|
|
fprintf(stderr, "Success ACCOUNT\n");
|
|
|
|
if (!LoadSQLConfig("SQL_COMMON", "METIN2_COMMON_SQL", &common_sql))
|
|
return false;
|
|
|
|
if (!ConnectDatabase(SQL_COMMON, common_sql, "common"))
|
|
return false;
|
|
|
|
fprintf(stderr, "Success COMMON\n");
|
|
|
|
if (!LoadSQLConfig("SQL_HOTBACKUP", "METIN2_HOTBACKUP_SQL", &hotbackup_sql))
|
|
return false;
|
|
|
|
if (!ConnectDatabase(SQL_HOTBACKUP, hotbackup_sql, "hotbackup"))
|
|
return false;
|
|
|
|
fprintf(stderr, "Success HOTBACKUP\n");
|
|
|
|
if (!CNetPoller::instance().Create())
|
|
{
|
|
sys_err("Cannot create network poller");
|
|
return false;
|
|
}
|
|
|
|
sys_log(0, "ClientManager initialization.. ");
|
|
|
|
if (!CClientManager::instance().Initialize())
|
|
{
|
|
sys_log(0, " failed");
|
|
return false;
|
|
}
|
|
|
|
sys_log(0, " OK");
|
|
|
|
if (!PlayerHB::instance().Initialize())
|
|
{
|
|
sys_err("cannot initialize player hotbackup");
|
|
return false;
|
|
}
|
|
|
|
LogStartupSummary(heart_beat, iIDStart);
|
|
|
|
#ifndef OS_WINDOWS
|
|
signal(SIGUSR1, emergency_sig);
|
|
#endif
|
|
signal(SIGSEGV, emergency_sig);
|
|
return true;
|
|
}
|
|
|
|
void SetTablePostfix(const char* c_pszTablePostfix)
|
|
{
|
|
if (!c_pszTablePostfix || !*c_pszTablePostfix)
|
|
g_stTablePostfix = "";
|
|
else
|
|
g_stTablePostfix = c_pszTablePostfix;
|
|
}
|
|
|
|
const char * GetTablePostfix()
|
|
{
|
|
return g_stTablePostfix.c_str();
|
|
}
|
|
|
|
void SetPlayerDBName(const char* c_pszPlayerDBName)
|
|
{
|
|
if (! c_pszPlayerDBName || ! *c_pszPlayerDBName)
|
|
g_stPlayerDBName = "";
|
|
else
|
|
{
|
|
g_stPlayerDBName = c_pszPlayerDBName;
|
|
g_stPlayerDBName += ".";
|
|
}
|
|
}
|
|
|
|
const char * GetPlayerDBName()
|
|
{
|
|
return g_stPlayerDBName.c_str();
|
|
}
|