需求場景
- 源目錄持續產生文件 (命名前綴
Test_)
- 文件格式:
主叫|被叫|開始時間|結束時間| (管道分隔)
- 需實時生成控制文件
CtlFile.txt:最早開始時間|最晚結束時間|總記錄數|
- 處理完成文件移至備份目錄,避免重複處理
- 定期掃描 (每分鐘),長期運行
系統架構設計
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ SrcFile/ │────▶│ C 程式常駐 │────▶│ CtlFile/ │
│ Test_*.txt │ │ 掃描→解析→統計 │ │ CtlFile.txt │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌──────────────┐
│ SrcFile_bak/ │
│ 已處理文件 │
└──────────────┘
核心模塊劃分 (三文件結構)
| 文件 | 職責 | 關鍵函數 |
| main.c | 入口、初始化、主循環 | main() |
| CtlFileCreate.h | 數據結構、類型定義、函數聲明 | T_SrcFileContent, GetValueFromStr... |
| CtlFileCreate.c | 核心邏輯實現 | ReadSrcFileAndWriteCtlFile, GetSrcFileFieldValue... |
關鍵數據結構
// 源文件單條記錄
typedef struct {
UINT8 szSrcNumber[50]; // 主叫
UINT8 szDstNumber[50]; // 被叫
UINT8 szDataStartTime[50]; // 開始時間
UINT8 szDataEndTime[50]; // 結束時間
} T_SrcFileContent;
// 全局統計狀態
UINT8 g_szDataStartTime[50] = {0}; // 全域最早開始時間
UINT8 g_szDataEndTime[50] = {0}; // 全域最晚結束時間
UINT32 g_iRecordsSum = 0; // 總記錄數
核心算法邏輯
1. 字段提取 (通用分隔符解析)
BOOL GetValueFromStr(UINT16 iSerialNum, UINT8 iContentType,
UINT8 *pSourceStr, UINT8 *pDstStr,
UINT8 cIsolater, UINT32 iDstStrSize) {
// 定位第 iSerialNum 個字段 (以 cIsolater 分隔)
UINT8 *pStrBegin = pSourceStr;
while (--iSerialNum) {
pStrBegin = strchr(pStrBegin, cIsolater);
if (!pStrBegin) return FALSE;
pStrBegin++;
}
UINT8 *pStrEnd = strchr(pStrBegin, cIsolater);
if (!pStrEnd) return FALSE;
// 複製字段內容
UINT32 len = pStrEnd - pStrBegin;
UINT8 szRetBuf[1024];
memcpy(szRetBuf, pStrBegin, len);
szRetBuf[len] = '\0';
// 類型轉換
switch (iContentType) {
case MML_STR_TYPE: strncpy(pDstStr, szRetBuf, iDstStrSize); break;
case MML_INT32_TYPE: *(UINT32*)pDstStr = atoi(szRetBuf); break;
// ... INT8, INT16
}
return TRUE;
}
2. 增量統計更新
void GetCtlFileContentAndWrite(T_SrcFileContent *src, UINT8 *buf) {
// 更新最早開始時間
if (strlen(g_szDataStartTime) == 0 ||
strcmp(g_szDataStartTime, src->szDataStartTime) > 0) {
strcpy(g_szDataStartTime, src->szDataStartTime);
}
// 更新最晚結束時間
if (strlen(g_szDataEndTime) == 0 ||
strcmp(g_szDataEndTime, src->szDataEndTime) < 0) {
strcpy(g_szDataEndTime, src->szDataEndTime);
}
// 總數累加
g_iRecordsSum++;
// 格式化輸出
snprintf(buf, 500, "%s|%s|%d|",
g_szDataStartTime, g_szDataEndTime, g_iRecordsSum);
}
3. 主掃描循環
void ReadSrcFileAndWriteCtlFile() {
while (1) {
DIR *dir = opendir(g_szSourceDir);
struct dirent *ent;
while ((ent = readdir(dir))) {
if (strncmp(ent->d_name, "Test_", 5) == 0) {
char fullpath[500];
snprintf(fullpath, sizeof(fullpath), "%s/%s",
g_szSourceDir, ent->d_name);
GetSrcFileContentAndWriteCtlFile(fullpath);
// 移動到備份目錄 (原子操作)
char cmd[500];
snprintf(cmd, sizeof(cmd), "mv %s %s", fullpath, g_szSourceBakDir);
system(cmd);
}
}
closedir(dir);
Sleep(60 * 1000); // 休眠 1 分鐘
}
}
4. 文件行解析流程
void GetSrcFileContentAndWriteCtlFile(char *filename) {
FILE *fp = fopen(filename, "r");
char line[1024];
T_SrcFileContent record;
while (fgets(line, sizeof(line), fp)) {
RemoveLineEnd(line); // 去除 \r\n
if (strlen(line) == 0) continue;
GetSrcFileFieldValue(line, &record); // 解析 4 個字段
char ctl_line[500];
GetCtlFileContentAndWrite(&record, ctl_line); // 更新統計
WriteToCtlFile(ctl_line); // 覆寫控制文件
}
fclose(fp);
}
編譯與部署
# 編譯
gcc -g -o CtlFileCreate main.c CtlFileCreate.c
# 目錄結構準備
mkdir -p ~/zhouzhaoxiong/zzx/CtlFileCreate/{SrcFile,CtlFile,SrcFile_bak}
# 投放測試文件到 SrcFile/
# 運行
./CtlFileCreate &
# 查看結果
cat ~/zhouzhaoxiong/zzx/CtlFileCreate/CtlFile/CtlFile.txt
# 輸出示例: 20151101 110909|20160109 091545|5|
關鍵設計亮點
| 特性 | 實現方式 | 價值 |
| 增量處理 | 全局變量維護最值、計數 | O(1) 更新,無需全量重算 |
| 去重保障 | 處理後 mv 到備份目錄 | 原子移動,避免重複處理/數據丟失 |
| 容錯設計 | fopen 失敗重試、字段解析邊界檢查 | 生產環境穩健運行 |
| 配置外部化 | 路徑、前綴、間隔可改為配置文件 | 無需重編譯適應環境變化 |
| 跨平台類型 | UINT8/UINT32 等自定義類型 | 消除平台差異 |
改進建議
- inotify 替換輪詢:Linux 內核事件通知,毫秒級響應,零 CPU 空轉
- 配置文件化:路徑、間隔、前綴移至 INI/JSON 配置
- 日誌系統:引入 syslog 或輕量日誌庫,分級記錄
- 信號處理:捕獲 SIGTERM/SIGINT 優雅關閉 (刷新緩衝、釋放資源)
- 並發安全:若多實例運行,需文件鎖 (flock) 保護控制文件寫入
- 時間解析:將字串時間轉為 time_t 比較,避免字典序陷阱 (如 20151103 vs 20151201)
- 單元測試:針對 GetValueFromStr、時間比較、邊界條件編寫測試