C++ 守护进程(Daemon Process)
1. 项目实现思路
守护进程(Daemon Process)是指在后台运行的进程,通常不与终端交互,常用于处理系统任务或服务,例如日志记录、网络监听等。守护进程在系统启动时启动,并持续运行,直到系统关闭或进程被手动停止。
在类Unix系统(如Linux)中,守护进程通常会:
- 与终端断开连接。
- 运行在后台并脱离父进程。
- 具有独立的进程ID(PID)。
- 将标准输入输出重定向到文件或设备。
守护进程的创建一般包括:
- 创建新的会话:使进程与终端分离,成为一个新的会话的领头进程。
- 更改工作目录:通常将工作目录切换到
/
,避免守护进程占用文件系统。 - 重定向标准文件描述符:将
stdin
、stdout
、stderr
重定向到某个日志文件。 - 关闭文件描述符:关闭不再需要的文件描述符。
通过以上步骤,守护进程就可以在后台正常工作。
2. 项目步骤
2.1 创建守护进程
- 在创建守护进程时,我们首先会调用
fork()
创建一个子进程,然后在子进程中调用setsid()
创建新的会话,从而使进程与终端分离,成为守护进程。 - 将工作目录更改为根目录
/
,避免占用系统磁盘。 - 重定向标准输入输出流(
stdin
、stdout
、stderr
)到指定文件,通常会把它们指向一个日志文件。 - 使用
umask(0)
确保文件权限不受影响。
2.2 守护进程任务
守护进程通常会执行一些长期运行的任务,例如监控系统资源、定期任务等。我们将在守护进程中模拟一个简单的定时任务。
3. 实现代码
以下是C++实现守护进程的基本步骤,创建一个守护进程并让它周期性地记录日志。
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctime>
void createDaemon() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
std::cerr << "Fork failed!" << std::endl;
exit(1);
}
if (pid > 0) {
exit(0); // 父进程退出,子进程继续运行
}
// 创建新的会话
if (setsid() < 0) {
std::cerr << "Failed to create session!" << std::endl;
exit(1);
}
// 更改工作目录为根目录
if (chdir("/") < 0) {
std::cerr << "Failed to change directory!" << std::endl;
exit(1);
}
// 关闭标准输入输出
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 重定向标准输出到日志文件
std::ofstream logFile("/tmp/daemon_log.txt", std::ios::app);
if (!logFile) {
std::cerr << "Failed to open log file!" << std::endl;
exit(1);
}
// 守护进程任务:记录时间戳到日志文件
while (true) {
time_t now = time(0);
char* dt = ctime(&now);
logFile << "Daemon running at: " << dt;
logFile.flush();
sleep(5); // 每5秒记录一次
}
logFile.close();
}
int main() {
createDaemon();
return 0;
}
4. 代码解读
4.1 createDaemon()
函数
-
创建子进程:
fork()
:创建一个子进程。如果fork()
成功,它会返回两次:一次在父进程中返回子进程的PID,另一次在子进程中返回0。父进程会退出,子进程继续运行。
-
创建新的会话:
setsid()
:调用此函数将子进程从当前终端会话中分离,成为一个新的会话的领头进程。这样,子进程就成为了一个守护进程,和父进程及终端断开了关联。
-
更改工作目录:
chdir("/")
:将工作目录改为根目录/
,这样守护进程就不会在挂载的文件系统中占用目录(例如/tmp
)。
-
关闭文件描述符:
close(STDIN_FILENO)
、close(STDOUT_FILENO)
、close(STDERR_FILENO)
:关闭标准输入输出流,因为守护进程不需要与终端交互。
-
重定向标准输出到日志文件:
- 我们打开
/tmp/daemon_log.txt
日志文件,并将日志写入此文件中。logFile.flush()
确保每次日志都会被立即写入磁盘。
- 我们打开
-
守护进程任务:
- 守护进程每5秒记录一次当前的时间戳到日志文件中,模拟一个周期性的后台任务。
4.2 main()
函数
main()
函数调用createDaemon()
来创建守护进程,并开始执行任务。
5. 项目总结
这个简单的守护进程模拟了一个周期性任务,每5秒记录当前时间到日志文件。通过使用fork()
和setsid()
,我们实现了一个脱离终端的守护进程,并通过chdir()
和close()
确保它不依赖于终端和文件系统的状态。
守护进程的关键点:
- 脱离终端:通过
setsid()
使进程成为新的会话的领头进程,并从终端断开。 - 工作目录:通常将工作目录设置为根目录,以避免占用系统中的其他目录。
- 重定向标准输入输出:守护进程通常不会与终端交互,因此需要将
stdin
、stdout
、stderr
重定向到文件或设备。 - 后台运行:守护进程会持续运行并执行任务,直到被显式终止。
通过这些步骤,守护进程能够在后台稳定运行,适用于需要长时间运行的服务或任务。