539
魔獸
Linux下程序啟動之後的初始化---檢查配置文件及讀取日誌配置項的值
**概述 **
最近,我對本開發組的幾位新員工所編寫的程序進行了代碼走查,發現他們的代碼都有一個共同的問題:缺少必要的初始化。在本文中,我將詳細介紹程序在啟動時所必須要進行的初始化操作,並用實際的C代碼予以說明。
對於一般的程序來說,在啟動時所必須要進行的初始化操作有兩個:檢查配置文件及讀取日誌配置項。對於檢查配置文件,主要檢查配置文件是否是規定的文件類型(後綴是否正確)及是否存放在了規定的目錄下(一般存放在當前用戶的etc目錄下);對於讀取日誌配置項,主要將寫日誌文件相關的參數(如日誌級別、日誌文件最大長度、最大備份日誌文件數量等)從配置文件的日誌段中讀取出來,放到全局變量中,以便後續流程使用。
從上圖可以看出,程序啟動之後,如果檢查配置文件和讀取日誌配置項兩者之一不通過,那麼程序就不會執行後續流程。由此也可以看到,初始化操作在程序中的重要地位。下麵我們用實際的C代碼來說明上圖中所示的兩個初始化操作。
**程序代碼 **
為了便於說明,假設我們的主程序文件名為InitEnv.c,配置文件名為InitEnv.ini。同時,因為要從配置文件中讀取日誌配置項的值,我們編寫GetConfig.c和GetConfig.h文件來提供從文件中讀取配置項值的操作(也就是API)。三個文件的代碼內容如下:
InitEnv.c:
/**********************************************************************
* 版權所有 (C)2016, Zhou Zhaoxiong
*
* 文件名稱:InitEnv.c
* 文件標識:無
* 內容摘要:程序運行之前檢查配置文件和日誌文件是否存在
* 其它說明:無
* 當前版本:V1.0
* 作 者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include "GetConfig.h"
// 宏定義
#define INI_FILE_NAME "InitEnv.ini"
#define LOG_FILE_NAME "InitEnv.log"
// 日誌模塊全局參數結構
typedef struct
{
UINT32 iLoglevel; // 日誌級別
UINT32 iMaxLogSize; // 日誌文件最大長度
UINT32 iMaxBakCount; // 最大備份日誌文件數量
UINT32 iNewLogFileFlag; // 啟動時是否新建空日誌文件, 否則追加到上次的日誌文件中,1-Yes 0-No
UINT32 iLogPositionFlag; // 是否輸出日誌位置信息(文件名/行號),1-Yes 0-No
UINT8 szLogFileFullName[256]; // 日誌文件全路徑名稱
} T_LogInfo;
T_LogInfo t_loginfo = {0};
// 函數聲明
INT32 AccessCfgFile(UINT8 *pszCfgFileName, UINT8 *pszCfgFileFullName);
INT32 InitLogInfo(UINT8 *pszCfgFileFullName);
/****************************************************************
* 功能描述: 主函數
* 輸入參數: 無
* 輸出參數: 無
* 返 回 值: 0-執行完成
* 其他說明: 無
* 修改日期 版本號 修改人 修改內容
*-------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
****************************************************************/
INT32 main(void)
{
INT32 iRetVal = 0;
UINT8 szCfgFileFullName[256] = {0};
// 首先檢查配置文件是否存在,並獲取帶全路徑的配置文件名
iRetVal =AccessCfgFile(INI_FILE_NAME, szCfgFileFullName);
if (iRetVal !=0) // 配置文件不存在, 直接返回
{
printf("exec AccessCfgFile failed!\n");
return -1;
}
// 打印獲取到的帶全路徑的配置文件名
printf("CfgFileFullName is %s!\n", szCfgFileFullName);
// 然後讀取配置文件, 初始化日誌信息
iRetVal =InitLogInfo(szCfgFileFullName);
if (iRetVal !=0) // 讀取配置文件失敗, 直接返回
{
printf("exec InitLogInfo failed!\n");
return -1;
}
// 打印初始化的日誌信息
printf("Loglevel is %d, MaxLogSize is %d(MB), MaxBakCount is %d,NewLogFileFlag is %d, LogPositionFlag is %d, LogFileFullName is %s!\n",t_loginfo.iLoglevel, t_loginfo.iMaxLogSize, t_loginfo.iMaxBakCount,
t_loginfo.iNewLogFileFlag, t_loginfo.iLogPositionFlag,t_loginfo.szLogFileFullName);
return 0;
}
/****************************************************************
* 功能描述: 檢查配置文件是否存在,並獲取帶全路徑的配置文件名
* 輸入參數: pszCfgFileName-不帶路徑的配置文件名
* 輸出參數: pszCfgFileFullName-帶全路徑的配置文件名
* 返 回 值: 0-存在 -1-不存在 -2-程序處理異常
* 其他說明: 無
* 修改日期 版本號 修改人 修改內容
*-------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
****************************************************************/
INT32 AccessCfgFile(UINT8 *pszCfgFileName, UINT8*pszCfgFileFullName)
{
UINT8 szTmpCfgFileName[256] = {0};
UINT8 *pFindStr = NULL;
if (NULL ==pszCfgFileName || NULL == pszCfgFileFullName)
{
printf("AccessCfgFile: pszCfgFileName or pszCfgFileFullName isNULL!\n");
return -2;
}
// 判斷配置文件的後綴是否為ini
pFindStr =strstr(pszCfgFileName, ".ini");
if (pFindStr ==NULL) // 配置文件後綴錯誤,直接返回
{
printf("AccessCfgFile: the suffix of %s is not ini, pleasecheck!\n", pszCfgFileName);
return -2;
}
// 獲取帶全路徑的配置文件名
snprintf(szTmpCfgFileName, sizeof(szTmpCfgFileName)-1,"%s/etc/%s", getenv("HOME"), pszCfgFileName);
// 判斷配置文件是否存在
if (0 == access(szTmpCfgFileName,F_OK)) // 配置文件存在
{
snprintf(pszCfgFileFullName, sizeof(szTmpCfgFileName)-1, "%s",szTmpCfgFileName);
}
else
{
printf("AccessCfgFile: %s has not existed!\n",szTmpCfgFileName);
return -1;
}
return 0;
}
/****************************************************************
* 功能描述: 讀取配置文件, 初始化日誌信息
* 輸入參數: pszCfgFileFullName-帶全路徑的配置文件名
* 輸出參數: 無
* 返 回 值: 0-處理成功 -1-處理失敗
* 其他說明: 無
* 修改日期 版本號 修改人 修改內容
* -------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
****************************************************************/
INT32 InitLogInfo(UINT8 *pszCfgFileFullName)
{
if (NULL ==pszCfgFileFullName)
{
printf("InitLogInfo:pszCfgFileFullName is NULL!\n");
return -1;
}
// 日誌級別0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-Debug 6-All
t_loginfo.iLoglevel= GetConfigFileIntValue("LOG", "LogLevel", 3,pszCfgFileFullName);
// 日誌文件最大長度, 單位MB, 範圍是[1,500]
t_loginfo.iMaxLogSize = GetConfigFileIntValue("LOG","LogMaxSize", 5, pszCfgFileFullName);
if(t_loginfo.iMaxLogSize < 1 || t_loginfo.iMaxLogSize > 500)
{
t_loginfo.iMaxLogSize = 10; // 配置超出[1,500]範圍默認10M
}
// 最大備份日誌文件數量, 範圍是[1,999]
t_loginfo.iMaxBakCount = GetConfigFileIntValue("LOG","BackupCount", 10, pszCfgFileFullName);
if(t_loginfo.iMaxBakCount < 1 || t_loginfo.iMaxBakCount > 999)
{
t_loginfo.iMaxBakCount = 10; //配置超出[1,999]範圍默認10個
}
// 每次啟動是否新建空日誌文件
t_loginfo.iNewLogFileFlag = GetConfigFileIntValue("LOG","NewLogFileFlag", 1, pszCfgFileFullName);
// 是否輸出日誌位置信息(文件名/行號)標誌
t_loginfo.iLogPositionFlag = GetConfigFileIntValue("LOG","LogPositionFlag", 1, pszCfgFileFullName);
// 記錄日誌文件全路徑名稱
snprintf(t_loginfo.szLogFileFullName,sizeof(t_loginfo.szLogFileFullName)-1, "%s/log/%s",getenv("HOME"), LOG_FILE_NAME);
return 0;
}
GetConfig.h:
/**********************************************************************
* 版權所有 (C)2016, Zhou Zhaoxiong。
*
* 文件名稱:GetConfig.h
* 文件標識:無
* 內容摘要:Linux下配置文件的讀取
* 其它說明:無
* 當前版本:V1.0
* 作 者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
// 數據類型重定義
typedef unsigned char UINT8;
typedef signed int INT32;
typedef unsigned int UINT32;
// 函數聲明
void GetStringContentValue(FILE *fp, UINT8 *pszSectionName,UINT8 *pszKeyName, UINT8 *pszOutput, UINT32 iOutputLen);
void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8*pszConfigFileName);
INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName);
GetConfig.c:
/**********************************************************************
* 版權所有 (C)2016, Zhou Zhaoxiong。
*
* 文件名稱:GetConfig.c
* 文件標識:無
* 內容摘要:Linux下配置文件的讀取
* 其它說明:無
* 當前版本:V1.0
* 作 者:ZhouZhaoxiong
* 完成日期:20161213
*
**********************************************************************/
#include "GetConfig.h"
/**********************************************************************
* 功能描述:獲取具體的字符串值
* 輸入參數: fp-配置文件指針
pszSectionName-段名, 如: GENERAL
pszKeyName-配置項名, 如:EmployeeName
iOutputLen-輸出緩存長度
* 輸出參數: pszOutput-輸出緩存
* 返 回 值:無
* 其它說明:無
* 修改日期 版本號 修改人 修改內容
*------------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
********************************************************************/
void GetStringContentValue(FILE *fp, UINT8 *pszSectionName,UINT8 *pszKeyName, UINT8 *pszOutput, UINT32 iOutputLen)
{
UINT8 szSectionName[100] = {0};
UINT8 szKeyName[100] = {0};
UINT8 szContentLine[256] = {0};
UINT8 szContentLineBak[256] = {0};
UINT32 iContentLineLen = 0;
UINT32 iPositionFlag = 0;
// 先對輸入參數進行異常判斷
if (fp == NULL ||pszSectionName == NULL || pszKeyName == NULL || pszOutput == NULL)
{
printf("GetStringContentValue: input parameter(s) isNULL!\n");
return;
}
sprintf(szSectionName, "[%s]", pszSectionName);
strcpy(szKeyName,pszKeyName);
while (feof(fp) ==0)
{
memset(szContentLine, 0x00, sizeof(szContentLine));
fgets(szContentLine, sizeof(szContentLine), fp); // 獲取段名
// 判斷是否是注釋行(以;開頭的行就是注釋行)或以其他特殊字符開頭的行
if(szContentLine[0] == ';' || szContentLine[0] == '\r' || szContentLine[0] =='\n' || szContentLine[0] == '\0')
{
continue;
}
// 匹配段名
if(strncasecmp(szSectionName, szContentLine, strlen(szSectionName)) == 0)
{
while(feof(fp) == 0)
{
memset(szContentLine, 0x00,sizeof(szContentLine));
memset(szContentLineBak, 0x00, sizeof(szContentLineBak));
fgets(szContentLine, sizeof(szContentLine), fp); // 獲取字段值
// 判斷是否是注釋行(以;開頭的行就是注釋行)
if(szContentLine[0] == ';')
{
continue;
}
memcpy(szContentLineBak, szContentLine, strlen(szContentLine));
// 匹配配置項名
if(strncasecmp(szKeyName, szContentLineBak, strlen(szKeyName)) == 0)
{
iContentLineLen = strlen(szContentLine);
for(iPositionFlag = strlen(szKeyName); iPositionFlag <= iContentLineLen;iPositionFlag ++)
{
if (szContentLine[iPositionFlag] == ' ')
{
continue;
}
if (szContentLine[iPositionFlag] == '=')
{
break;
}
iPositionFlag = iContentLineLen + 1;
break;
}
iPositionFlag = iPositionFlag + 1; // 跳過=的位置
if(iPositionFlag > iContentLineLen)
{
continue;
}
memset(szContentLine, 0x00, sizeof(szContentLine));
strcpy(szContentLine, szContentLineBak + iPositionFlag);
// 去掉內容中的無關字符
for(iPositionFlag = 0; iPositionFlag < strlen(szContentLine); iPositionFlag ++)
{
if (szContentLine[iPositionFlag] == '\r' || szContentLine[iPositionFlag]== '\n' || szContentLine[iPositionFlag] == '\0')
{
szContentLine[iPositionFlag] = '\0';
break;
}
}
// 將配置項內容拷貝到輸出緩存中
strncpy(pszOutput, szContentLine, iOutputLen-1);
break;
}
else if(szContentLine[0] == '[')
{
break;
}
}
break;
}
}
}
/**********************************************************************
* 功能描述:從配置文件中獲取字符串
* 輸入參數: pszSectionName-段名, 如:GENERAL
pszKeyName-配置項名, 如:EmployeeName
pDefaultVal-默認值
iOutputLen-輸出緩存長度
pszConfigFileName-配置文件名
* 輸出參數: pszOutput-輸出緩存
* 返 回 值:無
* 其它說明:無
* 修改日期 版本號 修改人 修改內容
*------------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
********************************************************************/
void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8*pszConfigFileName)
{
FILE *fp = NULL;
UINT8 szWholePath[256] = {0};
// 先對輸入參數進行異常判斷
if (pszSectionName== NULL || pszKeyName == NULL || pszOutput == NULL || pszConfigFileName ==NULL)
{
printf("GetConfigFileStringValue: input parameter(s) isNULL!\n");
return;
}
// 獲取默認值
if (pDefaultVal ==NULL)
{
strcpy(pszOutput, "");
}
else
{
strcpy(pszOutput, pDefaultVal);
}
// 打開配置文件
fp =fopen(pszConfigFileName, "r");
if (fp == NULL)
{
printf("GetConfigFileStringValue: open %s failed!\n",szWholePath);
return;
}
// 調用函數用於獲取具體配置項的值
GetStringContentValue(fp, pszSectionName, pszKeyName, pszOutput,iOutputLen);
// 關閉文件
fclose(fp);
fp = NULL;
}
/**********************************************************************
* 功能描述:從配置文件中獲取整型變量
* 輸入參數:pszSectionName-段名, 如:GENERAL
pszKeyName-配置項名, 如:EmployeeName
iDefaultVal-默認值
pszConfigFileName-配置文件名
* 輸出參數:無
* 返 回 值:iGetValue-獲取到的整數值 -1-獲取失敗
* 其它說明:無
* 修改日期 版本號 修改人 修改內容
*------------------------------------------------------------------
* 20161213 V1.0 Zhou Zhaoxiong 創建
********************************************************************/
INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName)
{
UINT8 szGetValue[512] = {0};
INT32 iGetValue = 0;
// 先對輸入參數進行異常判斷
if (pszSectionName== NULL || pszKeyName == NULL || pszConfigFileName == NULL)
{
printf("GetConfigFileIntValue: input parameter(s) isNULL!\n");
return -1;
}
GetConfigFileStringValue(pszSectionName, pszKeyName, NULL, szGetValue,512-1, pszConfigFileName); // 先將獲取的值存放在字符型緩存中
if (szGetValue[0] =='\0' || szGetValue[0] == ';') // 如果是結束符或分號, 則使用默認值
{
iGetValue =iDefaultVal;
}
else
{
iGetValue =atoi(szGetValue);
}
return iGetValue;
}
**程序說明 **
我們主要對InitEnv.c文件進行說明:
第一,檢查配置文件的操作是由AccessCfgFile函數完成的,該函數首先判斷配置文件的後綴是否為ini,然後獲取帶全路徑的配置文件名,最後判斷配置文件是否存在。如果該函數執行失敗(配置文件不存在或其他),那麼直接停止程序的運行,不再執行後續流程。
第二,讀取日誌配置項的操作是由InitLogInfo函數完成的,該函數從配置文件InitEnv.ini的[LOG]段中將LogLevel、LogMaxSize、BackupCount、NewLogFileFlag和LogPositionFlag配置項的值讀取出來。如果該函數執行失敗,那麼直接停止程序的運行,不再執行後續流程。
第三,如果AccessCfgFile和InitLogInfo函數都執行成功,那麼就意味著程序初始化成功了,可以執行後續操作。本程序的後續操作便是打印讀取到的配置項的值及日誌文件全路徑名稱。
**程序測試 **
我們將上述三個文件上傳到Linux機器上,並使用“gcc -g -o InitEnvInitEnv.c GetConfig.c”命令編譯之後,生成InitEnv文件。執行“InitEnv”命令,即可對程序進行測試。
1)當配置文件InitEnv.ini不存在或未被放置到規定目錄時,程序運行結果如下:
AccessCfgFile: /home/zhou/etc/InitEnv.ini has not existed!
exec AccessCfgFile failed!
2)當配置文件InitEnv.ini內容如下:
[LOG]
;LogLevel, 0-Fatal 1-Error 2-Warn 3-Info4-Trace 5-Debug 6-All
LogLevel=4
;Max log size (MB), [1,500]
LogMaxSize=10
;Max log backup count [1,999]
BackupCount=100
;If output log into new file when starting, 1-Yes 0-No
NewLogFileFlag=0
;If output position info(filename/linenum), 1-Yes 0-No
LogPositionFlag=1
程序運行結果為:
CfgFileFullName is /home/zhou/etc/InitEnv.ini!
Loglevel is 4, MaxLogSize is 10(MB), MaxBakCount is 100,NewLogFileFlag is 0, LogPositionFlag is 1, LogFileFullName is /home/zhou/log/InitEnv.log!
可見,在正常情況下,程序能夠完成檢查配置文件及讀取日誌配置項的操作,大家也可以對程序進行更多的測試。
**總結 **
在程序進行具體的操作之前,一些初始化操作是必不可少的。本文中的示例隻是涉及到檢查配置文件和讀取日誌配置項兩個操作,在實際的應用中可能還會有初始化數據庫參數、建立與其他模塊的通信鏈路等操作,這要視不同的程序而定。
“磨刀不誤砍柴工”,當程序完成了必要的初始化操作之後,便可以進行正式的操作了。
最後更新:2017-09-24 20:32:49