嵌入式Linux网络编程:守护进程开发(自动重启+多客户端支持)


嵌入式Linux网络编程:守护进程实战开发(自动重启+多客户端支持)

【本文代码已在Linux平台验证通过】


一、守护进程核心需求

1.1 功能特性

守护进程
后台运行
自动重启
多客户端支持
自我防护
3秒重启间隔
并发处理
防误杀机制

1.2 技术指标

指标参数要求
重启响应时间≤3秒
最大并发连接数1024(可配置)
内存占用<10MB(空载)
CPU占用率<1%(空闲状态)

二、系统架构设计

2.1 守护进程双进程模型

// 主守护进程
pid_t daemon_pid = fork();
if (daemon_pid == 0) {
    // 监控进程
    while (1) {
        pid_t server_pid = fork();
        if (server_pid == 0) {
            // 业务服务进程
            start_server();
            exit(EXIT_SUCCESS);
        } else {
            // 监控服务进程状态
            waitpid(server_pid, &status, 0);
            sleep(3); // 3秒后重启
        }
    }
}

2.2 网络服务架构

客户端 服务进程 守护进程 connect() 心跳检测 状态监控 连接断开 重启服务 客户端 服务进程 守护进程

2.3 关键函数解析

函数用途
fork()创建一个子进程,子进程会复制父进程的地址空间
setsid()创建一个新的会话,使进程成为无控制终端的会话首进程
umask()设置文件创建权限掩码,确保守护进程创建的文件权限符合预期
dup2()将标准输出(stdout)和标准错误(stderr)重定向到日志文件
lockf()给文件 network_daemon.lock 加锁,防止多个守护进程同时运行
syslog()向系统日志(/var/log/syslog/var/log/messages)写入日志

三、完整代码实现

3.1 守护进程核心代码(daemon.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syslog.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <stdbool.h>

#define LOCK_FILE   "/var/run/network_daemon.lock"  // 进程锁文件,防止重复运行
#define SERVER_FILE "/home/szsoft02/gooosunwy/TaiShanPai/LinuxSDK/1_code_test/socket/daemon/server"  // 服务端执行文件路径

static int create_lock(void);
static void signal_handler(int sig);

static int lockfd = -1; // 锁文件描述符
static pid_t spid ;     // 服务进程PID
bool is_shutdown = false;   // 是否关闭守护进程

// 守护进程初始化
static void daemon_init(void)
{
    

    pid_t pid = fork();                 // 创建子进程
    if (pid < 0) exit(EXIT_FAILURE);    // fork失败退出
    if (pid > 0) exit(EXIT_SUCCESS);    // 父进程退出,子进程成为孤儿进程
    
    // 创建新会话,使子进程成为会话首进程,脱离终端
    if (setsid() < 0) exit(EXIT_FAILURE);

    // 第二次fork(),防止守护进程重新获取终端
    if(((pid=fork()) < 0)){
        exit(EXIT_FAILURE);
    } else if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    // 设置文件权限掩码,确保守护进程创建的文件有合适的权限
    // 这里不屏蔽任何权限,所有用户都可以修改新文件,按需设置
    umask(0);

    // 修改工作目录为根目录
    if(chdir("/") < 0) exit(EXIT_FAILURE);
    
    // 关闭所有打开的文件描述符
    for (int x = 0; x <= sysconf(_SC_OPEN_MAX); x++){
        close(x);
    }
    openlog("daemon_server", LOG_PID, LOG_DAEMON);

    // 单例检测放在两次fork后才可起作用
    if (create_lock() != 0) {
        syslog(LOG_ERR, "守护进程已在运行,退出...");
        exit(EXIT_FAILURE);
    }
    // 注册信号处理
    signal(SIGTERM, signal_handler);    // 终止信号
    signal(SIGCHLD, SIG_IGN);   // 避免子进程成为僵尸进程

    syslog(LOG_INFO, "守护进程启动成功, PID=%d", getpid());

}

// 创建锁文件, 防止多个守护进程实例同时运行
static int create_lock(void)
{
    lockfd = open(LOCK_FILE, O_RDWR|O_CREAT, 0640);
    if (lockfd < 0) return -1;  // 打开失败,可能是无权限

    // 尝试锁定文件,如果失败则说明已有实例在运行
    if (lockf(lockfd, F_TLOCK, 0) < 0) return -1;
    
    // 写入当前进程PID到锁文件
    char str[16];
    sprintf(str, "%d\n", getpid());
    write(lockfd, str, strlen(str));
    syslog(LOG_INFO, "守护进程首次运行");
    return 0;
}

// 信号处理
static void signal_handler(int sig)
{
    syslog(LOG_INFO, "收到信号: %d", sig);
    if (sig == SIGTERM) {   // 收到终止信号
        syslog(LOG_INFO, "守护进程退出");

        // 释放 PID 文件锁
        if (lockfd != -1) {
            close(lockfd);
            unlink(LOCK_FILE);
        }
        // 终止服务进程
        is_shutdown = true;
        kill(spid, SIGTERM);
        
        exit(EXIT_SUCCESS);
    }
}

// 服务进程监控
void monitor_loop(void)
{
    while (1) {
        spid = fork();
        if (spid == 0) {         // 子进程运行实际服务
            // 执行服务端程序
            syslog(LOG_INFO, "服务端进程启动...");
            char *argv[] = {"daemon_server", NULL};
            execve(SERVER_FILE, argv, NULL);
            syslog(LOG_ERR, "服务端进程启动失败");
            exit(EXIT_FAILURE);
        } else if (spid > 0) {   // 父进程监控
            int status;
            waitpid(spid, &status, 0);   // 等待子进程退出
            if (is_shutdown) {
                syslog(LOG_NOTICE, "子进程已被回收,即将关闭syslog连接,守护进程退出");
                closelog();
                exit(EXIT_SUCCESS);
            }
            syslog(LOG_WARNING, "服务进程退出, 3秒后重启...");
            sleep(3);
        } else {
            syslog(LOG_ERR, "创建子进程失败");
            sleep(5);           // 创建失败等待5秒
        }
    }
}

int main(void)
{
    // 初始化守护进程
    daemon_init();
    monitor_loop(); // 进入服务进程监控循环
    
    syslog(LOG_INFO, "守护进程退出");
    closelog();     // 关闭日志
    return EXIT_SUCCESS;
}

3.2 网络服务代码(server.c)

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#include <syslog.h>

#define SERVER_SOCKPORT 8888                // 设置服务端端口号
#define BACKLOG         5                   // 如果连接数超过 BACKLOG 新连接请求将被拒绝(或延迟处理)
#define MAXLENGTH       256                 // 最大字节长度

#define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

int sockfd;

static void *read_from_client_handle(void *arg)
{
    int client_fd = *(int *)arg;

    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 NULL;
    }

    // 判断内存是否分配成功
    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 NULL;
    }

    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: 已收到数据:%s", getpid(),read_buf);
        memset(read_buf, 0, 1024);
        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 NULL;
}

/**
 * @brief 信号函数,回收所有退出的子进程,避免僵尸进程 
*/
static void avoid_zombie(int sig)
{
    pid_t pid;
    int status;
    char buf[1024];
    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);
    syslog(LOG_NOTICE, "释放syslog连接,服务端进程终止");
    closelog();

    // 退出
    exit(EXIT_SUCCESS);
}

int main(int argc,char *argv[])
{
    int res;
    struct sockaddr_in server_sockaddr,client_sockaddr;
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);

    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));

    // 服务端
    server_sockaddr.sin_family  = AF_INET;  // (域名)网络层端点协议:ipv4
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_sockaddr.sin_port = htons(SERVER_SOCKPORT);  // 设置服务端端口号

    // 客户端网络编程流程
    // 1.为通信创建端点
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    // 2.创建好客户端地址结构,绑定
    res = bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr));
    // 3.监听
    listen(sockfd, BACKLOG);

    // 注册信号函数,回收退出的子进程
    signal(SIGCLD,avoid_zombie);
    // 处理SIGTERM函数
    signal(SIGTERM, sigterm_handler);

    // 4.多连接服务端
    while(1){
        int clientfd = accept(sockfd, (struct sockaddr *)&client_sockaddr,&client_sockaddr_len);
        pid_t pid = fork();     // 创建一个子进程
        if(pid < 0){
            perror("fork");
        }else if(pid == 0){     // 在子进程内进行收客户端数据                        
            // 子进程不需要处理sockfd,释放文件描述符,使其引用计数减一
            close(sockfd);
            char buf[1024] = {0};
            sprintf(buf, "与客户端 from %s at PORT %d 文件描述符 %d 建立连接\n",
                    inet_ntoa(client_sockaddr.sin_addr), ntohs(client_sockaddr.sin_port), clientfd);
            syslog(LOG_INFO, "%s", buf);
            memset(buf, 0, 1024);

            sprintf(buf, "新的服务端pid为: %d\n", getpid());
            syslog(LOG_INFO, "%s", buf);
            
            read_from_client_handle((void *)&clientfd);

            // 释放资源并终止子进程
            close(clientfd);
            exit(EXIT_SUCCESS);
        }else{
            // 释放掉父进程不用的资源
            close(clientfd);
        }
    }

    printf("释放资源\n");
    close(sockfd);
    return 0;
}

四、编译与部署

4.1 编译命令

# 工作目录为:/home/szsoft02/gooosunwy/TaiShanPai/LinuxSDK/1_code_test/socket/daemon/
# 这里需要改动为你的工作目录
# 编译守护进程
gcc daemon.c -o daemon

# 编译服务进程 可执行文件名为 server , 若要改动,请把daemon.c代码中的SERVER_FILE修改
gcc server.c -o server

4.2 部署步骤

# 1.建立一个终端用来查看系统日志
sudo tail -F /var/log/syslog

# 2. 启动守护进程-必须使用sudo权限执行,因为创建锁路径为 "/var/run/network_daemon.lock" ,基本都需要sudo权限
# 建立另一个终端
sudo ./daemon

# 3. 查看进程树  或者  普通列举查看进程
pstree -p | grep daemon
或者
ps -ef
# 进程树上能找到 daemon(有个ID)---server(有个ID)
# 普通列举上能找到  daemon 和 daemon_server

# 3. 测试客户端连接
可以自己写一个客户端测试代码,只需要连接本例的端口 SERVER_SOCKPORT (8888) 和本机地址 127.0.0.1
文章最后有完整代码链接

# 4. 测试自动重启
sudo kill <server的ID>  # 观察3秒后是否重启

# 5. 彻底停止服务
sudo kill <daemon的ID>

五、关键技术解析

5.1 守护进程核心步骤

fork创建子进程
setsid创建新会话
关闭标准IO
设置文件掩码
改变工作目录
写PID文件

5.2 自动重启机制

void monitor_loop() {
    while (1) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程运行服务
            char *argv[] = {"daemon_server", NULL};
            execve(SERVER_FILE, argv, NULL);
            exit(EXIT_FAILURE);
        } else {
            // 父进程等待子进程退出
            waitpid(pid, &status, 0);
            sleep(3); // 等待3秒后重启
        }
    }
}

5.3 多客户端处理

// 使用fork为每个客户端创建独立进程
while (1) {
    client_fd = accept(...);
    pid = fork();
    if (pid == 0) {
        // 子进程处理客户端
        handle_client(client_fd);
        exit(0);
    }
    close(client_fd);
}

六、环境增强建议

6.1 安全性增强

# 设置权限(仅允许root操作)
sudo chown root:root daemon
sudo chmod 700 daemon

6.2 资源限制

// 设置最大文件描述符数
struct rlimit lim = {.rlim_cur = 65535, .rlim_max = 65535};
setrlimit(RLIMIT_NOFILE, &lim);

七、常见问题排查

7.1 端口占用错误

# 查看端口占用情况
sudo netstat -tulnp | grep :8888

# 释放端口
sudo fuser -k 8888/tcp

7.2 自动重启失效

# 检查inotify限制
cat /proc/sys/fs/inotify/max_user_instances

# 临时修改限制
echo 65536 | sudo tee /proc/sys/fs/inotify/max_user_instances

7.3 僵尸进程处理

// 添加SIGCHLD处理
signal(SIGCHLD, [](int){
    while (waitpid(-1, NULL, WNOHANG) > 0);
});

资源下载:
只取客户端代码连接本地


通过本方案可实现高可靠的网络服务守护进程,满足嵌入式系统对服务稳定性的严苛要求。后续可扩展实现双机热备、配置热加载等高级功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

银河码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值