当前位置: 首页 > news >正文

C语言在Linux中开发没有界面纯后台运行的Demo程序(含日志和Timer)

C语言在Linux中开发没有界面纯后台运行的Demo程序(含日志和Timer)

完全剥离终端界面依赖,仅保留 日志系统 + 精准周期定时器 + 安全启停控制,适合作为底层服务/守护进程的基础骨架。

纯后台运行的C语言开发的程序基础骨架

如有业务逻辑需接入 OnTimer,直接填充即可。

效果图:

image

 工程目录

image

 直接上完整代码

MyCServer.c

#include <stdio.h> //标准输入输出 终端/文件 I/O 基础
#include <stdlib.h> //通用工具与内存管理 进程控制、随机数、字符串转换
#include <string.h> //C 字符串与内存操作 不处理动态分配,仅操作已分配内存
#include <ctype.h>  //字符属性判断与转换 安全处理 ASCII/扩展字符
#include <locale.h> //本地化/国际化设置 影响数字格式、字符排序、ctype 行为
#include <time.h>   // 必须包含时间头文件
#include <sys/time.h>  // 提供 gettimeofday()
#include <signal.h>   // 后台运行 要在unistd.h上面
#include <unistd.h>    // 提供 usleep()/*====================== 显示常量 ======================*/
#define SKIP_LINE       3       // 显示起始行
#define LOOP_TIME       1000     // 定时器周期 (ms) 1000毫秒/*====================== 全局变量声明 ======================*/
extern int              bSetTime;               // 定时器标志
extern pthread_t        Th;                     // 定时器线程/*====================== 函数声明 ======================*/
int     InitSys(void);
void    ExitSys(void);
void    OnTimer(void);
void    SetTimer(pthread_t *pTh);
void    KillTimer(pthread_t Th);
void    *timer_sr(void *timer_func);

MyCServer.c

#include "MyCServer.h"     // 主头文件(包含 TRUNK_CHANNEL 等结构体定义和系统头文件)
#include "Logger.h"          // 日志模块/*====================== 全局变量 ======================*/
// 此处为全局变量实际分配内存
int     bSetTime = 0;       // 定时器标志
pthread_t Th = 0;           // 定时器线程(初始化为 0,表示未创建)// 全局安全退出标志
volatile sig_atomic_t g_running = 1;// 信号处理函数
void handle_signal(int sig) {(void)sig;g_running = 0; // 收到信号后修改标志,优雅退出
}int main(void){printf("Hello World\n");setvbuf(stdout, NULL, _IONBF, 0); // 关闭标准输出缓冲,日志实时打印// 初始化日志
    Log_Init();Log_Write("=======================================");Log_Write("======== MyCServer v1.88 启动 ==========");Log_Write("=======================================");if (InitSys() != 0){Log_Write("[MyCServer][错误] 系统初始化失败");Log_Close();return -1;}Log_Write("[MyCServer][信息] 系统初始化成功");//注册退出信号(Ctrl+C / kill / 终端关闭)signal(SIGINT, handle_signal);   // Ctrl+Csignal(SIGTERM, handle_signal);  // kill 命令默认信号signal(SIGHUP, handle_signal);   // 终端断开
printf("MyCServer 已启动 (PID: %d),将在后台运行...\n", getpid());//替换原 getch/getchar 循环,改为信号等待while (g_running) {sleep(1); // 让出 CPU,每秒检查一次退出标志
    }Log_Write("[信息] 收到退出信号,正在安全关闭...");ExitSys();Log_Close();printf("程序已安全退出。\n");return 0;
}/*====================== 系统初始化 ======================*/
int InitSys(void)
{int nEnableTimer = 1;     // Timer 开关:1=启动,其他值=不启动// 已移除:InitWin(); refresh();if (nEnableTimer == 1){SetTimer(&Th);}Log_Write("系统初始化完成===");return 0;
}/*====================== 系统退出 ======================*/
void ExitSys(void)
{Log_Write("正在关闭系统...");// 已移除:clear(); refresh(); endwin();if (bSetTime == 1){KillTimer(Th);Log_Write("[MyCServer][信息] Timer定时器已停止");}
}/*====================== 定时器回调 ======================*/
void OnTimer(void)
{// 已移除所有 ncurses 刷新与时间显示代码// 此处可放置纯业务逻辑,例如:// Log_Write("[Timer] 周期触发,执行心跳检查/数据轮询...");
}/*====================== 定时器管理 ======================*/
typedef void (*LP_V_V)(void);
void *timer_sr(void *timer_func); // 前向声明void SetTimer(pthread_t *pTh)
{bSetTime = 1;pthread_create(pTh, NULL, timer_sr, (void *)OnTimer);
}void KillTimer(pthread_t Th)
{if (Th == 0) return;if (bSetTime == 1){pthread_cancel(Th);bSetTime = 0;pthread_join(Th, NULL);}
}/*====================== 定时器工作线程 ======================*/
void *timer_sr(void *timer_func)
{struct timeval old, now;long ns, nus;LP_V_V tm_sr = (LP_V_V)timer_func;while (bSetTime){gettimeofday(&old, NULL);tm_sr();gettimeofday(&now, NULL);ns  = now.tv_sec  - old.tv_sec;nus = now.tv_usec - old.tv_usec;if (nus < 0) { ns--; nus += 1000000; }long remaining_us = LOOP_TIME * 1000 - (ns * 1000000 + nus);if (remaining_us > 0){// 使用 POSIX 标准 nanosleep 替代已废弃的 usleepstruct timespec ts;ts.tv_sec  = remaining_us / 1000000;ts.tv_nsec = (remaining_us % 1000000) * 1000;nanosleep(&ts, NULL);}}return NULL;
}

Logger.h

/** ============================================================================* Logger.h - 日志模块头文件* ============================================================================*/#ifndef LOGGER_H
#define LOGGER_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <pthread.h>/*====================== 日志配置 ======================*/
#define LOG_BASE_PATH       "./logs"         // 日志根目录
#define LOG_MAX_SIZE        (5 * 1024 * 1024) // 单个日志文件最大大小 (5MB)
#define LOG_PATH_MAX        512              // 路径缓冲区大小
#define LOG_BUFFER_MAX      1024             // 日志内容缓冲区大小
#define LOG_TIME_FORMAT_MAX 64               // 时间戳格式缓冲区大小/*====================== 日志句柄结构 ======================*/
typedef struct {FILE    *fp;                // 当前日志文件指针char    szFilePath[LOG_PATH_MAX];  // 当前日志文件路径time_t  tLastCheck;         // 上次检查时间long    lFileSize;          // 当前文件大小int     nLastHour;          // 上次小时int     nLastDay;           // 上次日期
} LOG_HANDLE;/*====================== 全局变量声明 ======================*/
extern LOG_HANDLE       g_Log;          // 全局日志句柄
extern pthread_mutex_t  g_LogMutex;     // 日志互斥锁/*====================== 函数声明 ======================*//*** 初始化日志系统* 创建日志目录并打开初始日志文件*/
void Log_Init(void);/*** 写入日志* @param szFormat 格式化字符串 (支持 printf 风格格式)* @param ... 可变参数*/
void Log_Write(const char *szFormat, ...);/*** 检查并切换日志文件 (按小时或文件大小轮转)*/
void Log_CheckAndRotate(void);/*** 关闭日志系统* 关闭日志文件并释放资源*/
void Log_Close(void);/*** 获取当前日志文件路径* @param szPath 输出路径缓冲区* @param size 缓冲区大小*/
void Log_GetFilePath(char *szPath, size_t size);/*** 获取文件大小* @param szPath 文件路径* @return 文件大小 (字节)*/
long Log_GetFileSize(const char *szPath);/*** 创建目录 (递归)* @param szPath 目录路径*/
void Log_CreateDir(const char *szPath);#endif /* SH_LOG_H */

Logger.c

/** ============================================================================* Logger.c - 日志模块实现* ============================================================================*/#include "Logger.h"
#include <stdarg.h>     // va_list, va_start, va_end/*====================== 类型定义 ======================*/
#ifndef BOOL_DEFINED
#define BOOL_DEFINED
typedef int BOOL;
#ifndef TRUE
#define TRUE    1
#endif
#ifndef FALSE
#define FALSE   0
#endif
#endif/*====================== 全局变量定义 ======================*/
LOG_HANDLE      g_Log;          // 全局日志句柄
pthread_mutex_t g_LogMutex;     // 日志互斥锁/*====================== 日志函数实现 ======================*//*** 创建目录(递归)* @param szPath 目录路径*/
void Log_CreateDir(const char *szPath)
{char szDir[LOG_PATH_MAX];char *p = szDir;strncpy(szDir, szPath, sizeof(szDir) - 1);szDir[sizeof(szDir) - 1] = '\0';for (p += 1; *p; p++){if (*p == '/'){*p = '\0';mkdir(szDir, 0755);*p = '/';}}mkdir(szDir, 0755);
}/*** 获取当前日志文件路径* 格式:./logs/YYYYMMDD/YYYYMMDD_HH.log* @param szPath 输出路径缓冲区* @param size 缓冲区大小*/
void Log_GetFilePath(char *szPath, size_t size)
{time_t tNow = time(NULL);struct tm *tmNow = localtime(&tNow);snprintf(szPath, size, "%s/%04d%02d%02d/%04d%02d%02d_%02d.log",LOG_BASE_PATH,tmNow->tm_year + 1900, tmNow->tm_mon + 1, tmNow->tm_mday,tmNow->tm_year + 1900, tmNow->tm_mon + 1, tmNow->tm_mday,tmNow->tm_hour);
}/*** 获取文件大小* @param szPath 文件路径* @return 文件大小 (字节),失败返回 0*/
long Log_GetFileSize(const char *szPath)
{struct stat st;if (stat(szPath, &st) == 0){return st.st_size;}return 0;
}/*** 初始化日志系统* 创建日志目录并打开初始日志文件*/
void Log_Init(void)
{memset(&g_Log, 0, sizeof(LOG_HANDLE));pthread_mutex_init(&g_LogMutex, NULL);// 创建日志根目录
    Log_CreateDir(LOG_BASE_PATH);// 打开日志文件
    Log_CheckAndRotate();
}/*** 检查并切换日志文件 (按小时或文件大小轮转)* 触发条件:* 1. 小时变化* 2. 日期变化* 3. 文件大小超过 5MB*/
void Log_CheckAndRotate(void)
{time_t tNow = time(NULL);struct tm *tmNow = localtime(&tNow);char szNewPath[LOG_PATH_MAX];// 检查是否需要切换文件(小时变化或文件过大)BOOL bNeedRotate = FALSE;if (g_Log.fp == NULL){bNeedRotate = TRUE;}else if (g_Log.nLastHour != tmNow->tm_hour || g_Log.nLastDay != tmNow->tm_mday){bNeedRotate = TRUE;  // 小时或日期变化
    }else if (g_Log.lFileSize >= LOG_MAX_SIZE){bNeedRotate = TRUE;  // 文件超过 5MB
    }if (!bNeedRotate){return;}// 关闭旧文件if (g_Log.fp != NULL){fclose(g_Log.fp);g_Log.fp = NULL;}// 生成新文件路径Log_GetFilePath(szNewPath, sizeof(szNewPath));// 创建日期目录char szDirPath[LOG_PATH_MAX];time_t tNow2 = time(NULL);struct tm *tmNow2 = localtime(&tNow2);snprintf(szDirPath, sizeof(szDirPath), "%s/%04d%02d%02d",LOG_BASE_PATH,tmNow2->tm_year + 1900, tmNow2->tm_mon + 1, tmNow2->tm_mday);Log_CreateDir(szDirPath);// 打开新文件g_Log.fp = fopen(szNewPath, "a");if (g_Log.fp != NULL){strncpy(g_Log.szFilePath, szNewPath, sizeof(g_Log.szFilePath) - 1);g_Log.lFileSize = Log_GetFileSize(szNewPath);g_Log.nLastHour = tmNow->tm_hour;g_Log.nLastDay = tmNow->tm_mday;g_Log.tLastCheck = tNow;}
}/*** 写入日志* @param szFormat 格式化字符串 (支持 printf 风格格式)* @param ... 可变参数*/
void Log_Write(const char *szFormat, ...)
{va_list args;char szBuffer[LOG_BUFFER_MAX];char szTime[LOG_TIME_FORMAT_MAX];time_t tNow = time(NULL);struct tm *tmNow = localtime(&tNow);// 生成时间戳snprintf(szTime, sizeof(szTime), "%04d-%02d-%02d %02d:%02d:%02d",tmNow->tm_year + 1900, tmNow->tm_mon + 1, tmNow->tm_mday,tmNow->tm_hour, tmNow->tm_min, tmNow->tm_sec);// 生成日志内容
    va_start(args, szFormat);int nLen = vsnprintf(szBuffer, sizeof(szBuffer), szFormat, args);va_end(args);// 加锁pthread_mutex_lock(&g_LogMutex);// 检查是否需要切换文件
    Log_CheckAndRotate();// 写入日志if (g_Log.fp != NULL){fprintf(g_Log.fp, "[%s] %s\n", szTime, szBuffer);fflush(g_Log.fp);g_Log.lFileSize += nLen + strlen(szTime) + 3;}// 解锁pthread_mutex_unlock(&g_LogMutex);
}/*** 关闭日志系统* 关闭日志文件并释放资源*/
void Log_Close(void)
{pthread_mutex_lock(&g_LogMutex);if (g_Log.fp != NULL){fclose(g_Log.fp);g_Log.fp = NULL;}pthread_mutex_unlock(&g_LogMutex);pthread_mutex_destroy(&g_LogMutex);
}

Makefile

# ==========================================
# 编译器与变量定义
# 原生单 *.c 文件的的编译命令  gcc MyCServer.c -o MyCServer
# 有Makefile文件的 清理并编译  make clean && make
# 运行  ./MyCServer
# ==========================================# 定义 C 语言编译器为 gcc
CC = gcc
# 定义 C++ 编译器为 g++ (虽然本文件主要编译 .c,但预留变量是个好习惯)
CXX = g++# 编译选项 (CFLAGS)
# -Wall                  : 开启几乎所有警告,帮助发现潜在代码问题
# -O2                    : 开启二级优化,提高程序运行速度
# -D_XOPEN_SOURCE_EXTENDED : 宏定义,启用 X/Open 标准扩展,通常用于支持宽字符和高级终端功能
# -D_GNU_SOURCE          : 宏定义,启用 GNU C 库的扩展功能,确保能使用一些非标准的系统调用
CFLAGS = -Wall -O2 -D_XOPEN_SOURCE_EXTENDED -D_GNU_SOURCE# 链接选项 (LDFLAGS)
# -lpthread              : 链接 pthread 库,支持多线程编程
# -lshpa3                : 链接 shpa3 库 (可能是你的私有库或特定硬件库)
# -lncursesw             : 链接 ncursesw 库,'w' 代表 wide-character,用于在终端支持中文和宽字符显示
# -lstdc++               : 链接 C++ 标准库 (因为可能混编了 C++ 代码或依赖库是用 C++ 写的)
# -lzmq                  : 链接 ZeroMQ 库,用于高性能网络消息通信
LDFLAGS = -lpthread -lshpa3 -lncursesw -lstdc++ -lzmq# ==========================================
# 2. 目标文件与源文件定义
# ==========================================# 最终生成的可执行文件名
#TARGET = ShPbxServer
TARGET = MyCServer# 源文件列表 (所有 .c 文件)
#SRCS = ShPbxServer.c Logger.c ZeroMQServer.c ShBoard.c ShMemRecord.c ZeroMQAudioPush.c
SRCS = MyCServer.c Logger.c# 目标文件列表 (自动将 SRCS 中的 .c 替换为 .o)
# 例如: ShPbxServer.c -> ShPbxServer.o
OBJS = $(SRCS:.c=.o)# ==========================================
# 3. 伪目标声明
# ==========================================# 声明 .PHONY 告诉 make,all 和 clean 不是文件名,而是动作标签
# 这样可以避免当前目录下有名为 "all" 或 "clean" 的文件时产生冲突
.PHONY: all clean install uninstall# ==========================================
# 4. 默认目标
# ==========================================# all 是默认执行的第一个目标,它依赖于 $(TARGET)
# 执行 make 时,会先尝试构建 $(TARGET)
all: $(TARGET)# ==========================================
# 5. 链接规则 (生成可执行文件)
# ==========================================# 规则:生成 $(TARGET) 依赖于所有的 $(OBJS)
$(TARGET): $(OBJS)# 注意:下面这行开头必须是一个 Tab 键,不能是空格!# 执行链接命令:将所有的 .o 文件链接在一起,生成最终的可执行文件$(CC) $(CFLAGS) -o $(TARGET) $(OBJS) $(LDFLAGS)# ==========================================
# 6. 编译规则 (生成 .o 文件)
# ==========================================# 模式规则:告诉 make 如何将任意 .c 文件编译成 .o 文件
# % 是通配符,表示匹配任意文件名
%.o: %.c# 执行编译命令:# -c 表示只编译不链接# $< 代表第一个依赖文件 (即源文件 .c)# $@ 代表目标文件 (即 .o 文件)$(CC) $(CFLAGS) -c $< -o $@# ==========================================
# 7. 清理规则
# ==========================================# 清理构建产物
clean:# 删除所有的 .o 中间文件和最终的可执行文件# -f 参数表示强制删除,不提示确认rm -f $(OBJS) $(TARGET)# ==========================================
# 8. 安装与卸载 (可选)
# ==========================================# 安装目标:将编译好的程序复制到系统路径,使其可以在任意目录运行
install: $(TARGET)cp $(TARGET) /usr/local/bin/# 卸载目标:删除系统路径中的程序
uninstall:rm -f /usr/local/bin/$(TARGET)#================几个关键点解释=================== 
#1、-lncursesw 的作用:
#你的程序中使用了 ncursesw 而不是普通的 ncurses。这说明你的程序界面需要显示中文或者特殊符号。如果编译报错找不到库,通常是因为系统没装 libncursesw-dev。
#2、$< 和 $@:
#这是 Makefile 的自动变量。
#$<:代表依赖列表中的第一个文件(在这里是 .c 源文件)。
#$@:代表目标文件(在这里是 .o 对象文件)。
#使用它们可以让规则通用,不用为每个文件单独写一行编译命令。
#3、Tab 键缩进:
#在 Makefile 中,所有命令(如 $(CC)... 或 rm...)前面必须使用 Tab 键 进行缩进,不能使用空格。这是新手最容易遇到的报错原因(missing separator)。
#4、$(SRCS:.c=.o):
#这是一个字符串替换函数。它会自动把 SRCS 变量里所有的 .c 后缀替换成 .o,这样你添加新源文件时,只需要改 SRCS 列表,不用手动去改 OBJS。
#================================================

进入工程目录

执行  清理且编译 命令

make clean && make

image

 运行命令

./MyCServer

image

 后台运行命令(生产环境)

nohup ./MyCServer > /dev/null 2>&1 &

image

 其中 [1] 1246333  为进程号  用于关闭程序

关闭程序

先查看进程状态

# 查看进程状态
ps aux | grep MyCServer
#
pgrep -a MyCServer

优雅关闭进程

# 优雅停止(会触发 handle_signal → 执行 ExitSys() 清理资源)
kill <PID>
# 或按名称停止
pkill MyCServer

image

后台运行命令

场景命令说明
日常调试 nohup ./MyCServer > run.log 2>&1 & 忽略终端关闭,控制台日志保存到 run.log
完全静默(生产环境) nohup ./MyCServer > /dev/null 2>&1 & 丢弃控制台输出(你的 Logger 已写文件时推荐)
彻底脱离 setsid ./MyCServer > /dev/null 2>&1 & 创建独立会话,完全不受父终端控制
临时测试 ./MyCServer & 仅放入后台,关闭终端会退出(不推荐生产)

管理后台进程

# 查看进程状态
ps aux | grep MyCServer
#
pgrep -a MyCServer# 优雅停止(会触发 handle_signal → 执行 ExitSys() 清理资源)
kill <PID>
# 或按名称停止
pkill MyCServer# 强制停止(不推荐,可能丢失日志或导致资源未释放)
kill -9 <PID># 查看控制台日志(若重定向到了文件)
tail -f run.log

 

http://www.jsqmd.com/news/682231/

相关文章:

  • ESP32项目实战:不用ESP32-CAM,如何将动态采集的JPG图片上传到巴法云?
  • nli-MiniLM2-L6-H768惊艳效果:跨语言(英→中)NLI迁移能力初步测试结果
  • 灵芝推荐的品牌有哪些 2026年值得关注的选择 - 品牌排行榜
  • 量子纠错技术:从比特到高维系统的演进与实践
  • 探索ExDark数据集:破解低光照计算机视觉挑战的创新解决方案
  • 重庆力冠衡器:屏山电子地磅出售公司找哪家 - LYL仔仔
  • 二分图匹配之匈牙利算法
  • Visual C++系统依赖库深度解析:Windows应用程序兼容性修复完整方案
  • 终极免费音频转换器fre:ac:5分钟从新手到高手的完整指南 [特殊字符]
  • 别再盲目加-Xmx!GraalVM静态镜像无JVM参数真相:内存布局由SubstrateVM在编译期固化——附内存映射图谱与12个关键-H选项解读
  • 深刻理解跨链技术 LayerZero:从超轻节点到全链未来的演进
  • 2026年4月净水加盟创业选型指南:家用直饮、商用净水、全屋净软水加盟品牌优选清单 - 海棠依旧大
  • 全国门业市场持续扩容,选对厂商是关键 - 深度智识库
  • 2026西安老房装修品牌推荐榜:十年家装老兵深度调研 - 深度智识库
  • 海南洪鑫再生资源回收:海口市废旧金属 电缆电线回收公司电话 - LYL仔仔
  • 2026年广西外墙仿石漆定制与全屋整装一站式服务深度指南 - 年度推荐企业名录
  • 2026年贵阳全屋整装定制与旧房改造服务对标深度横评 - 年度推荐企业名录
  • JimuReport积木报表:企业级数据可视化架构深度解析与实战指南
  • PCA主成分分析避坑指南:Excel计算中的5个常见错误与验证方法
  • 给《饥荒联机版》自制一件新衣服:从零开始的Mod制作保姆级教程(附完整代码与贴图处理)
  • 2026现阶段陕西公司注册服务商深度测评与选型指南 - 2026年企业推荐榜
  • 终极暗黑破坏神2存档编辑器指南:3分钟打造完美角色
  • 盘锦市再生物资回收:大洼县废品收购附近上门 - LYL仔仔
  • 杭州余杭永鸿再生资源回收:余杭区厂房拆除回收附近 - LYL仔仔
  • LPRNet车牌识别:5分钟部署轻量级高精度识别框架终极指南
  • 2026 年郑州新能源汽车服务全流程攻略:从选型到售后一站式指南 - 速递信息
  • Arducam PiNSIGHT AI相机板:树莓派5的视觉计算利器
  • 告别手动复制链接!在 WSL2 中配置 Jupyter 后台服务与密码登录的完整指南
  • Netflix 4K画质与杜比音效优化指南:3个关键技术方案
  • 猎头公司加盟,真的是割韭菜吗?深度解读南方新华加盟政策 - 榜单推荐