守护进程:
第一步:fork+退出父进程
父进程(你运行的原始程序)立刻fotk()出一个子进程,然后字节退出
为什么?因为shell(你敲命令的窗口)会以为这个程序已经结束了,不会卡住等它
子进程就会变成"孤儿",这就是我们想要的
目的:让进程脱离用户命令行,变成后台运行
第二步:调用setsid()
子进程调用setside(),创建一个全新的会话(session),自己当"会话老大"
这样他就彻底地和原来的终端断开联系了
原来的终端即使关闭(比如你关掉SSH或终端窗口),也不会发出信号(比如SIGHUP)把它干掉
目的:脱离控制终端,避免被用户操作意外杀死
第三步:再fork一次(可选且推荐)
再fork一次生成一个"孙子进程",原来的子进程退出
为什么,因为"会话老大"有资格重新获取一个控制终端(虽然我们不希望这样)
"孙子"不是老大,就永远没机会绑定中断了,更安全
目的:彻底断绝与终端的任何可能联系
第四步:把工作目录改成根目录/(chdir("/"))
比如你是在/home/you/project下启动的程序,如果不改目录,这个目录就不能被卸载
改成 / 后,就不会"占着茅坑不拉屎"
目的:避免阻碍文件系统卸载
第五步:重设文件权限掩码
每个进程都有一个叫umask的东西,它会偷偷"遮掉"你创建文件的某些权限。
比如你写open("log.txt", 0666),但umask是022,最后文件权限变成0644。
调用umask(0)就是说:"别遮,我要完全控制权限"
目的:确保守护进程创建的文件权限是精确可控的。
第六步:关闭文件描述符
守护进程通常不需要标准输入、输出和错误文件描述符,因为它们不与终端交互。关闭这些不需要的文件描述符可以避免资源泄露,提高守护进程的安全性和效率
目的:节省资源,提高安全性
第七步;处理信号
SIGHUP:虽然守护进程和终端断开,但仍然有可能收到其它进程或内核发来的SIGHUP信号,搜狐进程不应该因为它而终止。
SIGTERM:SIGTERM是终止信号,用于请求守护进程优雅地终止。通过命令行执行kill <pid>命令可以发哦是那个SIGTERM信号,接收到这个信号之后,守护进程终止子进程,并清理回收资源,最后退出
目的:既不能被误杀,又能被正常关闭
第八步:执行具体任务
这一步是守护进程的核心,它开始执行为其设计的特定功能,如监听网络请求,定期清理文件系统、执行系统备份等
/**
*@brief 如果调用进程不是进程组的领导者,则创建一个新的会话。创建者是新会话的领导者
*@return pid_t 成功则会返回调用进程的新会话ID,失败则返回(pid_t)-1,并设置errno以指明错误原因
*/
pid_t setsid(void);
/**
*@brief 设置调用进程的文件模式创建掩码
*@param mask 掩码。是一个八进制数,它hiding哪些权限位在文件或目录创建时应被关闭。我们通过umask(0)确保守护进程创建的文件和目录具有最开放的权限设置。
*@return mode_t 这个系统调用必然成功,返回之前的掩码值
*/
mode_t umask(mode_t mask);
/**
*@brief 更改调用进程的工作目录
*@param path 更改后的工作路径
*@return int 成功返回0,失败返回-1,并设置errno
*/
int chdir(const char *path);
------------------------daemon_test.c------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // UNIX标准函数,如fork, setsid, chdir等
#include <sys/types.h>
#include <sys/stat.h> // 文件状态,如umask
#include <sys/wait.h> // 等待子进程
#include <syslog.h>
#include <string.h>
#include <fcntl.h> // 文件控制,如open
#include <signal.h> // 信号处理
#include <string.h>
#include <errno.h> // 错误号
pid_t pid; // 全局变量,用于存储子进程(tcp_server)的pid
int is_shutdown = 0; // 标志位,表示是否收到SIGTERM信号准备退出
void signal_handler(int sig)
{
switch (sig)
{
case SIGHUP:
syslog(LOG_WARNING, "收到SIGHUP信号..."); // 忽略SIGHUP,只记录
break;
case SIGTERM:
syslog(LOG_NOTICE, "接收到终止信号,准备退出守护进程...");
syslog(LOG_NOTICE, "向子进程发送SIGTERM信号...");
is_shutdown = 1; // 设置标志位
kill(pid, SIGTERM); // 向子进程(tcp_server)发送SIGTERM
break;
default:
syslog(LOG_INFO, "Received unhandled signal");
}
}
void my_daemonize()
{
pid_t pid;
// 第一次fork:创建子进程,父进程退出
pid = fork();
if (pid < 0)
exit(EXIT_FAILURE); // fork失败
if (pid > 0)
exit(EXIT_SUCCESS); // 父进程退出,子进程继续
// 创建新会话,脱离终端
if (setsid() < 0)
exit(EXIT_FAILURE);
// 处理 SIGHUP、SIGTERM 信号
signal(SIGHUP, signal_handler);
signal(SIGTERM, signal_handler);
// 第二次fork:确保守护进程不是会话首进程,避免意外获取终端
pid = fork();
if (pid < 0)
exit(EXIT_FAILURE);
if (pid > 0)
exit(EXIT_SUCCESS); // 第一个子进程退出,第二个子进程继续
// 重置文件权限掩码
umask(0);
// 改变工作目录到根目录,避免占用启动目录
chdir("/");
// 关闭所有打开的文件描述符
for (int x = 0; x <= sysconf(_SC_OPEN_MAX); x++)
{
close(x);
}
// 打开系统日志,后续日志将写入系统日志
openlog("this is our daemonize process: ", LOG_PID, LOG_DAEMON);
}
int main()
{
my_daemonize(); // 将当前进程守护化
while (1) // 无限循环,守护进程主循环
{
pid = fork(); // 创建子进程,用于运行tcp_server
if (pid > 0) // 父进程(守护进程)
{
syslog(LOG_INFO, "守护进程正在监听服务端进程...");
waitpid(-1, NULL, 0); // 等待任意子进程退出(阻塞)
if (is_shutdown) {
syslog(LOG_NOTICE, "子进程已被回收,即将关闭syslog连接,守护进程退出");
closelog(); // 关闭日志
exit(EXIT_SUCCESS); // 退出守护进程
}
syslog(LOG_ERR, "服务端进程终止,3s后重启..."); // 如果子进程异常退出,则重启
sleep(3);
}
else if (pid == 0) // 子进程
{
syslog(LOG_INFO, "子进程fork成功");
syslog(LOG_INFO, "启动服务端进程");
char *path = "/home/daemon_and_multiplex/tcp_server"; // 服务器程序路径
char *argv[] = {"my_tcp_server", NULL}; // 参数列表,第一个参数是程序名
errno = 0;
execve(path, argv, NULL); // 执行服务器程序
// 如果execve失败,会继续执行下面的代码
char buf[1024];
sprintf(buf, "errno: %d", errno);
syslog(LOG_ERR, "%s", buf);
syslog(LOG_ERR, "服务端进程启动失败");
exit(EXIT_FAILURE);
}
else // fork失败
{
syslog(LOG_ERR, "子进程fork失败");
}
}
return EXIT_SUCCESS;
}
---------------------------tcp_server.c------------------------
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h> // 网络地址结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h> // 网络地址转换
#include <pthread.h>
#include <unistd.h> // 标准函数
#include <signal.h>
#include <syslog.h> // 系统日志
int sockfd; // 全局变量,服务器socket
void zombie_dealer(int sig)
{
pid_t pid;
int status;
char buf[1024];
memset(buf, 0, 1024);
// 一个SIGCHLD可能对应多个子进程的退出
// 使用while循环回收所有退出的子进程,避免僵尸进程的出现
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
{
if (WIFEXITED(status))
{
sprintf(buf, "子进程: %d 以 %d 状态正常退出,已被回收\n", pid, WEXITSTATUS(status));
syslog(LOG_INFO, "%s", buf);
}
else if (WIFSIGNALED(status))
{
sprintf(buf, "子进程: %d 被 %d 信号杀死,已被回收\n", pid, WTERMSIG(status));
syslog(LOG_INFO, "%s", buf);
}
else
{
sprintf(buf, "子进程: %d 因其它原因退出,已被回收\n", pid);
syslog(LOG_WARNING, "%s", buf);
}
}
}
void sigterm_handler(int sig) {
syslog(LOG_NOTICE, "服务端接收到守护进程发出的SIGTERM,准备退出...");
syslog(LOG_NOTICE, "释放sockfd");
close(sockfd); // 关闭监听socket
syslog(LOG_NOTICE, "释放syslog连接,服务端进程终止");
closelog(); // 关闭日志
// 优雅退出
exit(EXIT_SUCCESS);
}
void read_from_client_then_write(void *argv)
{
int client_fd = *(int *)argv; // 客户端socket
ssize_t count = 0, send_count = 0;
char *read_buf = NULL;
char *write_buf = NULL;
char log_buf[1024];
memset(log_buf, 0, 1024);
read_buf = malloc(sizeof(char) * 1024);
// 判断内存是否分配成功
if (!read_buf)
{
sprintf(log_buf, "服务端pid: %d: 读缓存创建异常,断开连接\n", getpid());
syslog(LOG_ERR, "%s", log_buf);
shutdown(client_fd, SHUT_WR); // 关闭写端
close(client_fd);
return;
}
// 判断内存是否分配成功
write_buf = malloc(sizeof(char) * 1024);
if (!write_buf)
{
sprintf(log_buf, "服务端pid: %d: 写缓存创建异常,断开连接\n", getpid());
syslog(LOG_ERR, "%s", log_buf);
free(read_buf); // 释放读缓冲区
shutdown(client_fd, SHUT_WR);
close(client_fd);
return;
}
// 循环接收客户端数据
while ((count = recv(client_fd, read_buf, 1024, 0)))
{
if (count < 0)
{
syslog(LOG_ERR, "server recv error");
}
sprintf(log_buf, "服务端pid: %d: reveive message from client_fd: %d: %s", getpid(), client_fd, read_buf);
syslog(LOG_INFO, "%s", log_buf);
memset(log_buf, 0, 1024);
// 构造回复消息
sprintf(write_buf, "服务端pid: %d: reveived~\n", getpid());
send_count = send(client_fd, write_buf, 1024, 0);
}
// 客户端关闭连接
sprintf(log_buf, "服务端pid: %d: 客户端client_fd: %d请求关闭连接......\n", getpid(), client_fd);
syslog(LOG_NOTICE, "%s", log_buf);
sprintf(write_buf, "服务端pid: %d: receive your shutdown signal\n", getpid());
send_count = send(client_fd, write_buf, 1024, 0);
// 清理资源
sprintf(log_buf, "服务端pid: %d: 释放client_fd: %d资源\n", getpid(), client_fd);
syslog(LOG_NOTICE, "%s", log_buf);
shutdown(client_fd, SHUT_WR);
close(client_fd);
free(read_buf);
free(write_buf);
return;
}
int main(int argc, char const *argv[])
{
int temp_result;
struct sockaddr_in server_addr, client_addr; // 服务器和客户端地址结构
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
// 声明IPV4通信协议
server_addr.sin_family = AF_INET;
// 我们需要绑定0.0.0.0地址,转换成网络字节序后完成设置
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 端口随便用一个,但是不要用特权端口
server_addr.sin_port = htons(6666);
// 创建server socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址
temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
// 进入监听模式
temp_result = listen(sockfd, 128);
socklen_t cliaddr_len = sizeof(client_addr);
// 注册信号处理函数,处理SIGCHLD信号,避免僵尸进程出现
signal(SIGCHLD, zombie_dealer);
// 处理SIGTERM函数,以优雅退出
signal(SIGTERM, sigterm_handler);
char log_buf[1024];
memset(log_buf, 0, 1024);
// 接受client连接
while (1)
{
int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &cliaddr_len);
pid_t pid = fork(); // 为每个客户端创建子进程
if (pid > 0)// 父进程
{
sprintf(log_buf, "this is father, pid is %d, continue accepting...\n", getpid());
syslog(LOG_INFO, "%s", log_buf);
memset(log_buf, 0, 1024);
// 父进程不需要处理client_fd,释放文件描述符,使其引用计数减一,以便子进程释放client_fd后,其引用计数可以减为0,从而释放资源
close(client_fd);
}
else if (pid == 0)
{
// 子进程不需要处理sockfd,释放文件描述符,使其引用计数减一
close(sockfd);
sprintf(log_buf, "与客户端 from %s at PORT %d 文件描述符 %d 建立连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);
syslog(LOG_INFO, "%s", log_buf);
memset(log_buf, 0, 1024);
sprintf(log_buf, "新的服务端pid为: %d\n", getpid());
syslog(LOG_INFO, "%s", log_buf);
memset(log_buf, 0, 1024);
// 读取客户端数据,并打印到 stdout
read_from_client_then_write((void *)&client_fd);
// 释放资源并终止子进程
close(client_fd);
exit(EXIT_SUCCESS);
}
}
return 0;
}
---------------------------tcp_client.c------------------------
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h> // 使用线程
#define handle_error(cmd, result) \
if (result < 0) \
{ \
perror(cmd); \
return -1; \
}
void *read_from_server(void *argv)
{
int sockfd = *(int *)argv;
char *read_buf = NULL;
ssize_t count = 0;
read_buf = malloc(sizeof(char) * 1024);
if (!read_buf)
{
perror("malloc client read_buf");
return NULL;
}
// 循环接收服务端数据
while (count = recv(sockfd, read_buf, 1024, 0))
{
if (count < 0)
{
perror("recv");
}
fputs(read_buf, stdout); // 输出到标准输出
}
printf("收到服务端的终止信号......\n");
free(read_buf);
return NULL;
}
void *write_to_server(void *argv)
{
int sockfd = *(int *)argv;
char *write_buf = NULL;
ssize_t send_count;
write_buf = malloc(sizeof(char) * 1024);
if (!write_buf)
{
printf("写缓存申请异常,断开连接\n");
shutdown(sockfd, SHUT_WR); // 关闭写端
perror("malloc client write_buf");
return NULL;
}
// 从标准输入读取数据
while (fgets(write_buf, 1024, stdin) != NULL)
{
send(sockfd, write_buf, 1024, 0);
if (send_count < 0)
{
perror("send");
}
}
printf("接收到命令行的终止信号,不再写入,关闭连接......\n");
shutdown(sockfd, SHUT_WR);
free(write_buf);
return NULL;
}
int main(int argc, char const *argv[])
{
int sockfd, temp_result;
pthread_t pid_read, pid_write; // 两个线程:读和写
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
// 连接本机 127.0.0.1
server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// 连接端口 6666
server_addr.sin_port = htons(6666);
// 创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
handle_error("socket", sockfd);
// 连接server
temp_result = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
handle_error("connect", temp_result);
// 启动一个子线程,用来读取服务端数据,并打印到 stdout
pthread_create(&pid_read, NULL, read_from_server, (void *)&sockfd);
// 启动一个子线程,用来从命令行读取数据并发送到服务端
pthread_create(&pid_write, NULL, write_to_server, (void *)&sockfd);
// 主线程等待子线程退出
pthread_join(pid_read, NULL);
pthread_join(pid_write, NULL);
printf("关闭资源\n");
close(sockfd);
return 0;
}
当前Linux系统的系统日志文件路径为 /var/log/syslog,执行命令tail -F /var/log/syslog进行实时日志监控
tail= 显示文件末尾的内容-F= 持续监控文件,即使文件被删除、重建、轮转(log rotation)也能继续跟踪/var/log/syslog= Linux 系统的主要日志文件(记录系统、服务、守护进程等日志)
