Linux系统中保持程序后台运行

前景提要

  之前的一篇博客有写过一个udp转发服务,详情可以点击这里查看。一开始,我们只会在需要使用的时候运行服务器上的程序。这种方法十分不方便,因为每次都要我远程连接服务器并使用终端运行程序,且终端关闭之后程序也会随之停止。基于此,我希望有方法能够将程序运行在后台,即使关闭了远程连接的终端也不会影响其正常使用。

程序示例

  这里写一个简单的程序示例来讲解和说明问题。主进程中包含两个线程,两个线程会一直输出各自的内容作为日志内容,源码如下所示:

#include <bits/stdc++.h>
#include <unistd.h>

class TT {
    //私有成员变量
private:
    //两个子线程
    std::thread* sayHelloWorld_thread;
    std::thread* sayHelloCpp_thread;
    //私有成员函数
private:
    //每隔一秒输出hello world
    void sayHelloWorld() {
        std::time_t currentTime;
        while (true) {
            currentTime = time(NULL);
            std::string words = "\n[--Log - SayHelloWorld--]: " + std::to_string(currentTime) + " - Hello world!\n";
            std::cout << words;
            sleep(1);
        }
    }
    //每隔一秒输出hello cpp
    void sayHelloCpp() {
        std::time_t currentTime;
        while (true) {
            currentTime = time(NULL);
            std::string words = "\n[--Log - SayHelloCpp--]: " + std::to_string(currentTime) + " - Hello cpp!\n";
            std::cout << words;
            sleep(1);
        }
    }

public:
    TT() {}
    ~TT() {
        if (sayHelloWorld_thread)
            delete sayHelloWorld_thread;
        if (sayHelloCpp_thread)
            delete sayHelloCpp_thread;
    }
    bool init() {
        sayHelloWorld_thread = new std::thread(&TT::sayHelloWorld, this);
        sayHelloCpp_thread = new std::thread(&TT::sayHelloCpp, this);
        return sayHelloWorld_thread && sayHelloCpp_thread;
    }
    void joinAll() {
        sayHelloWorld_thread->join();
        sayHelloCpp_thread->join();
    }
};

int main(int argc, char const* argv[]) {
    TT test;
    if (test.init()) {
        test.joinAll();
    }
    return 0;
}

  编译运行之后的结果如下所示:

[--Log - SayHelloWorld--]: 1651805753 - Hello world!

[--Log - SayHelloCpp--]: 1651805753 - Hello cpp!

[--Log - SayHelloWorld--]: 1651805754 - Hello world!

[--Log - SayHelloCpp--]: 1651805754 - Hello cpp!

[--Log - SayHelloWorld--]: 1651805755 - Hello world!

[--Log - SayHelloCpp--]: 1651805755 - Hello cpp!
......

使程序后台运行

nohup命令

  经过查看相关教程,我们知道可以使用nohup命令实现后台运行。nohup英文全称no hang up(不挂起),用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行。nohup命令,在默认情况下(非重定向时),会输出一个名叫nohup.out的文件到可执行程序的目录下,如果当前目录的 nohup.out 文件不可写,输出重定向到 $HOME/nohup.out文件中。
  该命令的格式如下所示:

nohup Command [ Arg … ] [ & ]

  Command:要执行的命令;
  Arg:一些参数,可以指定输出文件;
  &:让命令在后台执行,终端退出后命令依旧执行。
  使用nohup命令执行以上代码编译之后的可执行文件:

nohup /home/ubuntu/mycode/learncpp/bin/serverTest &

  以下为使用nohup命令执行上述程序的结果:

  显然,在可执行目录中出现了nohup.out文件,且其中也出现了程序的输出内容。此时,我们关闭终端是无法中断程序执行的,只能通过查看进程ID,然后使用kill命令终止进程。另外,在此过程中随着执行时间越来越长,我们的输出文件也会越来越大。

输出重定向

  有时候我们不希望输出内容出现在nohup.out中,我们希望能够将输出结果保存在我们希望的路径中。那么,我们可以使用重定向输出来指定输出文件。我们可以把以下三类输出重定向到其他文件:
  0——stdin(standard input,标准输入)
  1——stdout(standard output,标准输出)
  2——stderr(standard error,标准错误输出)
  使用方法如下所示:

# 将标准输出重定向到log.txt文件中
nohup /home/ubuntu/mycode/learncpp/bin/serverTest 1>/home/ubuntu/mycode/log/log.txt &

输出问题与解决方案

  由于输出文件越来越大且输出文件无法自动清理,我们不得不自己来控制输出。我的思路是为输出专门编写一个函数用于输出内容的文件写入,定期清理该文件即可。首先,我们使用时间戳来标定时间。一般情况下,输出函数使用追加的方式打开文件,然后写入内容;当时间达到设定的限制时,采用清空以前内容的方式打开文件,这样就可以实现对输出内容的定期清除。另外,可能不同的线程会同时调用输出函数,所以我们需要对文件读写进行加锁。
  基于此方案对源码进行修改,我们可以得到:

#include <bits/stdc++.h>
#include <unistd.h>

class TT {
    //私有成员变量
private:
    //两个子线程
    std::thread* sayHelloWorld_thread;
    std::thread* sayHelloCpp_thread;
    std::string logPath;                    // log文件目录
    std::mutex logFile_mutex;               // log文件的互斥量
    std::time_t lastTime;                   //前一时刻
    const int CLEARLOG_TIME_DELAY = 10;      //清空日志的时间间隔,单位为秒
    //私有成员函数
private:
    void writeLog(std::string log) {
        std::unique_lock<std::mutex> lk(logFile_mutex);
        time_t currentTime = std::time(0);
        std::ofstream output;
        //如果到达限定时间,则清空原有log文件
        if (currentTime - lastTime >= CLEARLOG_TIME_DELAY) {
            output.open(logPath, std::ofstream::out | std::ios_base::trunc);
            lastTime = currentTime;
        } else {
            output.open(logPath, std::ofstream::out | std::ios_base::app);
        }
        output << log;
        output.close();
    }
    void sayHelloWorld() {
        std::time_t currentTime;
        while (true) {
            currentTime = time(NULL);
            std::string words = "\n[--Log - SayHelloWorld--]: " + std::to_string(currentTime) + " - Hello world!\n";
            writeLog(words);
            sleep(1);
        }
    }
    void sayHelloCpp() {
        std::time_t currentTime;
        while (true) {
            currentTime = time(NULL);
            std::string words = "\n[--Log - SayHelloCpp--]: " + std::to_string(currentTime) + " - Hello cpp!\n";
            writeLog(words);
            sleep(1);
        }
    }

public:
    TT() {}
    ~TT() {
        if (sayHelloWorld_thread)
            delete sayHelloWorld_thread;
        if (sayHelloCpp_thread)
            delete sayHelloCpp_thread;
    }
    bool init(std::string logFilePath) {
        logPath = logFilePath + "/log.txt";
        lastTime = time(NULL);
        sayHelloWorld_thread = new std::thread(&TT::sayHelloWorld, this);
        sayHelloCpp_thread = new std::thread(&TT::sayHelloCpp, this);
        return sayHelloWorld_thread && sayHelloCpp_thread;
    }
    void joinAll() {
        sayHelloWorld_thread->join();
        sayHelloCpp_thread->join();
    }
};

int main(int argc, char const* argv[]) {
    TT test;
    //保存日志文件的目录
    std::string path = "/home/ubuntu/mycode/learncpp/tmp";
    if (test.init(path)) {
        test.joinAll();
    }
    return 0;
}

  通过这样的处理,可以使得所有的日志输出都保存在我们可控的文件并定期清理。即便是程序挂在后台也不会导致日志文件越来越大,同时也会在程序出现故障的时候尽可能的保留最近时间段内的输出内容。然而,如果程序刚好卡在清空的时间点出现了故障,可能就无法保留足够的输出日志供我们参考了,所以这种办法还不够好。
  目前来说,实现的效果如下所示:


当珍惜每一片时光~