惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
爱范儿
爱范儿
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
博客园 - 聂微东
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
T
Tailwind CSS Blog
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
罗磊的独立博客
Jina AI
Jina AI

博客园 - 海乐学习

VS2026创建.net8的控制台程序并能在银河麒麟V10的Liunx上运行 在麒麟系统中卸载RabbitMQ Java Maven 开发的常用命令 C语言开发的常用命令 三汇Linux配置Config说明 Java实现优雅的关闭程序并执行清理流程的写法 C语言开发中优雅的关闭程序并执行清理流程的写法 将C语言开发的程序做成 麒麟系统的服务(Systemd 标准服务,Linux 通用) 实现开机自启动 ZeroMQ的DEALER双帧结构(路由空帧 + 业务帧)(支持异步收发、主动推送事件)C写服务端 java写客户端 ZeroMQ中ZMQ_DEALER单帧数据(支持异步收发、主动推送事件)C写服务端 java写客户端 win10系统中 关闭专注助手 执行 xx.sh 脚本文件时出现: /bin/bash^M:解释器错误: 没有那个文件或目录 运行编译打包好的 xxx.jar 中没有主清单属性 出现这个错误 将Java编译的 .jar文件做成 麒麟系统的服务(Systemd 标准服务,Linux 通用) 实现开机自启动 将Java编译的 .jar文件做成windows服务 实现开机自启动 方法二 RabbitMQ在麒麟系统中离线安装说明 VMware启动虚似机后出现 无法获取快照信息: 锁定文件失败 模块“Snapshot”启动失败。未能启动虚拟机。 C语言在Linux中开发完整Demo包含读配置文件写日志和定时器Timer C语言在Linux中开发读取配置文件app.conf 麒麟V10 Server系统中搭建C语言开发环境 麒麟ServerV10 修改IP4地址 麒麟ServerV10 配置IP4 当系统中有两个版本的Maven时,用IDEA创建Maven有时会出错 麒麟ServerV10安装 espeak-ng 和 ffmpeg 方法 C语言在Linux中开发使用定时器Timer在界面上显示时间 C语言在Linux中开发带界面的程序(含每小时日志) C语言在Linux中开发第一个项目Hello Word 在apache-maven项目中使用log4写日志 在apache-maven项目中解决中文乱码问题 在apache-maven项目中读取配置文件
C语言在Linux中开发没有界面纯后台运行的Demo程序(含日志和Ti...
海乐学习 · 2026-04-22 · via 博客园 - 海乐学习

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+C
    signal(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 替代已废弃的 usleep
            struct 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。
#================================================

进入工程目录

执行  清理且编译 命令

image

 前台运行命令    Ctrl+C 则退出 或 关闭终端

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