Linux下程序啟動(dòng)之后的初始化---檢查配置文件及讀取日志配置項(xiàng)的值
概述
最近,我對(duì)本開(kāi)發(fā)組的幾位新員工所編寫(xiě)的程序進(jìn)行了代碼走查,發(fā)現(xiàn)他們的代碼都有一個(gè)共同的問(wèn)題:缺少必要的初始化。在本文中,我將詳細(xì)介紹程序在啟動(dòng)時(shí)所必須要進(jìn)行的初始化操作,并用實(shí)際的C代碼予以說(shuō)明。
對(duì)于一般的程序來(lái)說(shuō),在啟動(dòng)時(shí)所必須要進(jìn)行的初始化操作有兩個(gè):檢查配置文件及讀取日志配置項(xiàng)。對(duì)于檢查配置文件,主要檢查配置文件是否是規(guī)定的文件類(lèi)型(后綴是否正確)及是否存放在了規(guī)定的目錄下(一般存放在當(dāng)前用戶(hù)的etc目錄下);對(duì)于讀取日志配置項(xiàng),主要將寫(xiě)日志文件相關(guān)的參數(shù)(如日志級(jí)別、日志文件最大長(zhǎng)度、最大備份日志文件數(shù)量等)從配置文件的日志段中讀取出來(lái),放到全局變量中,以便后續(xù)流程使用。
可以用如下的流程圖來(lái)形象地表示程序的整個(gè)流程:
從上圖可以看出,程序啟動(dòng)之后,如果檢查配置文件和讀取日志配置項(xiàng)兩者之一不通過(guò),那么程序就不會(huì)執(zhí)行后續(xù)流程。由此也可以看到,初始化操作在程序中的重要地位。下面我們用實(shí)際的C代碼來(lái)說(shuō)明上圖中所示的兩個(gè)初始化操作。
程序代碼
為了便于說(shuō)明,假設(shè)我們的主程序文件名為InitEnv.c,配置文件名為InitEnv.ini。同時(shí),因?yàn)橐獜呐渲梦募凶x取日志配置項(xiàng)的值,我們編寫(xiě)GetConfig.c和GetConfig.h文件來(lái)提供從文件中讀取配置項(xiàng)值的操作(也就是API)。三個(gè)文件的代碼內(nèi)容如下:
InitEnv.c:
- /**********************************************************************
- * 版權(quán)所有 (C)2016, Zhou Zhaoxiong
- *
- * 文件名稱(chēng):InitEnv.c
- * 文件標(biāo)識(shí):無(wú)
- * 內(nèi)容摘要:程序運(yùn)行之前檢查配置文件和日志文件是否存在
- * 其它說(shuō)明:無(wú)
- * 當(dāng)前版本:V1.0
- * 作 者:ZhouZhaoxiong
- * 完成日期:20161213
- *
- **********************************************************************/
- #include "GetConfig.h"
- // 宏定義
- #define INI_FILE_NAME "InitEnv.ini"
- #define LOG_FILE_NAME "InitEnv.log"
- // 日志模塊全局參數(shù)結(jié)構(gòu)
- typedef struct
- {
- UINT32 iLoglevel; // 日志級(jí)別
- UINT32 iMaxLogSize; // 日志文件最大長(zhǎng)度
- UINT32 iMaxBakCount; // 最大備份日志文件數(shù)量
- UINT32 iNewLogFileFlag; // 啟動(dòng)時(shí)是否新建空日志文件, 否則追加到上次的日志文件中,1-Yes 0-No
- UINT32 iLogPositionFlag; // 是否輸出日志位置信息(文件名/行號(hào)),1-Yes 0-No
- UINT8 szLogFileFullName[256]; // 日志文件全路徑名稱(chēng)
- } T_LogInfo;
- T_LogInfo t_loginfo = {0};
- // 函數(shù)聲明
- INT32 AccessCfgFile(UINT8 *pszCfgFileName, UINT8 *pszCfgFileFullName);
- INT32 InitLogInfo(UINT8 *pszCfgFileFullName);
- /****************************************************************
- * 功能描述: 主函數(shù)
- * 輸入?yún)?shù): 無(wú)
- * 輸出參數(shù): 無(wú)
- * 返 回 值: 0-執(zhí)行完成
- * 其他說(shuō)明: 無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- *-------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ****************************************************************/
- 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;
- }
- /****************************************************************
- * 功能描述: 檢查配置文件是否存在,并獲取帶全路徑的配置文件名
- * 輸入?yún)?shù): pszCfgFileName-不帶路徑的配置文件名
- * 輸出參數(shù): pszCfgFileFullName-帶全路徑的配置文件名
- * 返 回 值: 0-存在 -1-不存在 -2-程序處理異常
- * 其他說(shuō)明: 無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- *-------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ****************************************************************/
- 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) // 配置文件后綴錯(cuò)誤,直接返回
- {
- 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;
- }
- /****************************************************************
- * 功能描述: 讀取配置文件, 初始化日志信息
- * 輸入?yún)?shù): pszCfgFileFullName-帶全路徑的配置文件名
- * 輸出參數(shù): 無(wú)
- * 返 回 值: 0-處理成功 -1-處理失敗
- * 其他說(shuō)明: 無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * -------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ****************************************************************/
- INT32 InitLogInfo(UINT8 *pszCfgFileFullName)
- {
- if (NULL ==pszCfgFileFullName)
- {
- printf("InitLogInfo:pszCfgFileFullName is NULL!\n");
- return -1;
- }
- // 日志級(jí)別0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-Debug 6-All
- t_loginfo.iLoglevel= GetConfigFileIntValue("LOG", "LogLevel", 3,pszCfgFileFullName);
- // 日志文件最大長(zhǎng)度, 單位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]范圍默認(rèn)10M
- }
- // 最大備份日志文件數(shù)量, 范圍是[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]范圍默認(rèn)10個(gè)
- }
- // 每次啟動(dòng)是否新建空日志文件
- t_loginfo.iNewLogFileFlag = GetConfigFileIntValue("LOG","NewLogFileFlag", 1, pszCfgFileFullName);
- // 是否輸出日志位置信息(文件名/行號(hào))標(biāo)志
- t_loginfo.iLogPositionFlag = GetConfigFileIntValue("LOG","LogPositionFlag", 1, pszCfgFileFullName);
- // 記錄日志文件全路徑名稱(chēng)
- snprintf(t_loginfo.szLogFileFullName,sizeof(t_loginfo.szLogFileFullName)-1, "%s/log/%s",getenv("HOME"), LOG_FILE_NAME);
- return 0;
- }
GetConfig.c:
- /**********************************************************************
- * 版權(quán)所有 (C)2016, Zhou Zhaoxiong。
- *
- * 文件名稱(chēng):GetConfig.h
- * 文件標(biāo)識(shí):無(wú)
- * 內(nèi)容摘要:Linux下配置文件的讀取
- * 其它說(shuō)明:無(wú)
- * 當(dāng)前版本:V1.0
- * 作 者:ZhouZhaoxiong
- * 完成日期:20161213
- *
- **********************************************************************/
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <dirent.h>
- #include <unistd.h>
- // 數(shù)據(jù)類(lèi)型重定義
- typedef unsigned char UINT8;
- typedef signed int INT32;
- typedef unsigned int UINT32;
- // 函數(shù)聲明
- 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:
- /**********************************************************************
- * 版權(quán)所有 (C)2016, Zhou Zhaoxiong。
- *
- * 文件名稱(chēng):GetConfig.c
- * 文件標(biāo)識(shí):無(wú)
- * 內(nèi)容摘要:Linux下配置文件的讀取
- * 其它說(shuō)明:無(wú)
- * 當(dāng)前版本:V1.0
- * 作 者:ZhouZhaoxiong
- * 完成日期:20161213
- *
- **********************************************************************/
- #include "GetConfig.h"
- /**********************************************************************
- * 功能描述:獲取具體的字符串值
- * 輸入?yún)?shù): fp-配置文件指針
- pszSectionName-段名, 如: GENERAL
- pszKeyName-配置項(xiàng)名, 如:EmployeeName
- iOutputLen-輸出緩存長(zhǎng)度
- * 輸出參數(shù): pszOutput-輸出緩存
- * 返 回 值:無(wú)
- * 其它說(shuō)明:無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- *------------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- 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;
- // 先對(duì)輸入?yún)?shù)進(jìn)行異常判斷
- 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); // 獲取段名
- // 判斷是否是注釋行(以;開(kāi)頭的行就是注釋行)或以其他特殊字符開(kāi)頭的行
- 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); // 獲取字段值
- // 判斷是否是注釋行(以;開(kāi)頭的行就是注釋行)
- if(szContentLine[0] == ';')
- {
- continue;
- }
- memcpy(szContentLineBak, szContentLine, strlen(szContentLine));
- // 匹配配置項(xiàng)名
- 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; // 跳過(guò)=的位置
- if(iPositionFlag > iContentLineLen)
- {
- continue;
- }
- memset(szContentLine, 0x00, sizeof(szContentLine));
- strcpy(szContentLine, szContentLineBak + iPositionFlag);
- // 去掉內(nèi)容中的無(wú)關(guān)字符
- for(iPositionFlag = 0; iPositionFlag < strlen(szContentLine); iPositionFlag ++)
- {
- if (szContentLine[iPositionFlag] == '\r' || szContentLine[iPositionFlag]== '\n' || szContentLine[iPositionFlag] == '\0')
- {
- szContentLine[iPositionFlag] = '\0';
- break;
- }
- }
- // 將配置項(xiàng)內(nèi)容拷貝到輸出緩存中
- strncpy(pszOutput, szContentLine, iOutputLen-1);
- break;
- }
- else if(szContentLine[0] == '[')
- {
- break;
- }
- }
- break;
- }
- }
- }
- /**********************************************************************
- * 功能描述:從配置文件中獲取字符串
- * 輸入?yún)?shù): pszSectionName-段名, 如:GENERAL
- pszKeyName-配置項(xiàng)名, 如:EmployeeName
- pDefaultVal-默認(rèn)值
- iOutputLen-輸出緩存長(zhǎng)度
- pszConfigFileName-配置文件名
- * 輸出參數(shù): pszOutput-輸出緩存
- * 返 回 值:無(wú)
- * 其它說(shuō)明:無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- *------------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- void GetConfigFileStringValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT8 *pDefaultVal, UINT8 *pszOutput, UINT32 iOutputLen, UINT8*pszConfigFileName)
- {
- FILE *fp = NULL;
- UINT8 szWholePath[256] = {0};
- // 先對(duì)輸入?yún)?shù)進(jìn)行異常判斷
- if (pszSectionName== NULL || pszKeyName == NULL || pszOutput == NULL || pszConfigFileName ==NULL)
- {
- printf("GetConfigFileStringValue: input parameter(s) isNULL!\n");
- return;
- }
- // 獲取默認(rèn)值
- if (pDefaultVal ==NULL)
- {
- strcpy(pszOutput, "");
- }
- else
- {
- strcpy(pszOutput, pDefaultVal);
- }
- // 打開(kāi)配置文件
- fp =fopen(pszConfigFileName, "r");
- if (fp == NULL)
- {
- printf("GetConfigFileStringValue: open %s failed!\n",szWholePath);
- return;
- }
- // 調(diào)用函數(shù)用于獲取具體配置項(xiàng)的值
- GetStringContentValue(fp, pszSectionName, pszKeyName, pszOutput,iOutputLen);
- // 關(guān)閉文件
- fclose(fp);
- fp = NULL;
- }
- /**********************************************************************
- * 功能描述:從配置文件中獲取整型變量
- * 輸入?yún)?shù):pszSectionName-段名, 如:GENERAL
- pszKeyName-配置項(xiàng)名, 如:EmployeeName
- iDefaultVal-默認(rèn)值
- pszConfigFileName-配置文件名
- * 輸出參數(shù):無(wú)
- * 返 回 值:iGetValue-獲取到的整數(shù)值 -1-獲取失敗
- * 其它說(shuō)明:無(wú)
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- *------------------------------------------------------------------
- * 20161213 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- INT32 GetConfigFileIntValue(UINT8 *pszSectionName, UINT8*pszKeyName, UINT32 iDefaultVal, UINT8 *pszConfigFileName)
- {
- UINT8 szGetValue[512] = {0};
- INT32 iGetValue = 0;
- // 先對(duì)輸入?yún)?shù)進(jìn)行異常判斷
- 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] == ';') // 如果是結(jié)束符或分號(hào), 則使用默認(rèn)值
- {
- iGetValue =iDefaultVal;
- }
- else
- {
- iGetValue =atoi(szGetValue);
- }
- return iGetValue;
- }
程序說(shuō)明
我們主要對(duì)InitEnv.c文件進(jìn)行說(shuō)明:
第一,檢查配置文件的操作是由AccessCfgFile函數(shù)完成的,該函數(shù)首先判斷配置文件的后綴是否為ini,然后獲取帶全路徑的配置文件名,最后判斷配置文件是否存在。如果該函數(shù)執(zhí)行失敗(配置文件不存在或其他),那么直接停止程序的運(yùn)行,不再執(zhí)行后續(xù)流程。
第二,讀取日志配置項(xiàng)的操作是由InitLogInfo函數(shù)完成的,該函數(shù)從配置文件InitEnv.ini的[LOG]段中將LogLevel、LogMaxSize、BackupCount、NewLogFileFlag和LogPositionFlag配置項(xiàng)的值讀取出來(lái)。如果該函數(shù)執(zhí)行失敗,那么直接停止程序的運(yùn)行,不再執(zhí)行后續(xù)流程。
第三,如果AccessCfgFile和InitLogInfo函數(shù)都執(zhí)行成功,那么就意味著程序初始化成功了,可以執(zhí)行后續(xù)操作。本程序的后續(xù)操作便是打印讀取到的配置項(xiàng)的值及日志文件全路徑名稱(chēng)。
程序測(cè)試
我們將上述三個(gè)文件上傳到Linux機(jī)器上,并使用“gcc -g -o InitEnvInitEnv.c GetConfig.c”命令編譯之后,生成InitEnv文件。執(zhí)行“InitEnv”命令,即可對(duì)程序進(jìn)行測(cè)試。
1)當(dāng)配置文件InitEnv.ini不存在或未被放置到規(guī)定目錄時(shí),程序運(yùn)行結(jié)果如下:
- AccessCfgFile: /home/zhou/etc/InitEnv.ini has not existed!
- exec AccessCfgFile failed!1212
2)當(dāng)配置文件InitEnv.ini內(nèi)容如下:
- [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
程序運(yùn)行結(jié)果為:
- 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!1212
可見(jiàn),在正常情況下,程序能夠完成檢查配置文件及讀取日志配置項(xiàng)的操作,大家也可以對(duì)程序進(jìn)行更多的測(cè)試。
總結(jié)
在程序進(jìn)行具體的操作之前,一些初始化操作是必不可少的。本文中的示例只是涉及到檢查配置文件和讀取日志配置項(xiàng)兩個(gè)操作,在實(shí)際的應(yīng)用中可能還會(huì)有初始化數(shù)據(jù)庫(kù)參數(shù)、建立與其他模塊的通信鏈路等操作,這要視不同的程序而定。
“磨刀不誤砍柴工”,當(dāng)程序完成了必要的初始化操作之后,便可以進(jìn)行正式的操作了。
【本文是51CTO專(zhuān)欄作者周兆熊的原創(chuàng)文章,作者微信公眾號(hào):周氏邏輯(logiczhou)】