You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

fork()与C++继承机制的冲突问题排查

问题:fork()生成的守护进程无法继承子类的虚函数实现

我用单个类同时实现服务端与客户端功能,但核心问题不在此。服务端构造函数中,我会先检查端口是否被占用,若未被占用则通过fork()创建后台守护进程,但生成的服务端始终是基类实例——子类重写的消息处理逻辑完全不生效。我怀疑fork()执行时子类尚未完成初始化,子进程继承的是基类状态,求确认该问题原因及可行解决方案。

注:子进程的输出目前无法正常工作,尝试将spdlog替换为标准iostream但操作有误未成功。


相关代码

//=============== HEADER ======================
#include <cstdint>
#include <vector>
#include <netinet/in.h>
#include <fstream>
#include <iostream>

#define DAEMON_BUFSIZE (1024)

class daemon {
private:
    int                             sockfd;
    struct sockaddr_in              servaddr;

    void init_logger(const char *fn="log.txt") {
        std::ofstream out("out.txt");
        std::streambuf *coutbuf = std::cout.rdbuf(); //save old buf
        std::cout.rdbuf(out.rdbuf()); //redirect std::cout to out.txt!
    }

    void server();
    void client();

protected:
    enum server_cmd {
        server_reply,
        server_noreply,
        server_teardown
    };

    virtual void init_server() {}
    virtual server_cmd handle_msg(std::vector<char> &msg)  {
        std::cout << "base class called!" << std::endl;
        return server_noreply;
    };

public:
    class exception : public std::exception {};

    explicit daemon(uint16_t port);
    daemon(const char *addr, uint16_t port);
    virtual ~daemon() = default;

    int send_msg(std::vector<char> &msg, bool needs_reply= false);

};

//=============== IMPLEMENTATION ======================

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>

static int check_daemon(uint16_t port) {
    int sockfd;

    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        std::cout << "[CRITICAL] server socket creation failed (should never happen): " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr{AF_INET, htons(port), INADDR_ANY};
    // Bind the socket with the server address
    if (bind(sockfd, (const struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        // port is busy, so daemon is running!
        std::cout << "daemon found" << std::endl;
        return -1;
    }
    return sockfd;
}

/**
 * Server constructor
 * @param port
 */
daemon::daemon(uint16_t port) {
    sockfd = check_daemon(port);
    if (sockfd <= 0) {
        throw exception();
    }

    auto pid = fork();
    if (pid < 0) {
        std::cout << "[CRITICAL] fork failed: " << strerror(errno) << std::endl;
    } else if (pid == 0) {
        init_logger();
        // child; daemonize
        server();
        exit(EXIT_SUCCESS);
    }
    // parent; wait for child
    sleep(1);   // should be done better
}

void daemon::server() {
    std::cout << "[INFO] starting daemon" << std::endl;
    ::daemon(1, 1);
    // here we are in daemon
    std::cout << "[INFO] server_daemon: initializing" << std::endl;

    init_server();

    struct sockaddr_in cliaddr{};
    char buff[DAEMON_BUFSIZE];
    bool running = true;
    std::cout << "[TRACE] server_daemon: entering loop" << std::endl;
    while (running) {
        socklen_t len = sizeof(cliaddr);
        auto n = recvfrom(sockfd, buff, DAEMON_BUFSIZE, MSG_WAITALL, (struct sockaddr*) &cliaddr, &len);
        if (n == -1) {
            std::cout << "[CRITICAL] recvfrom error (" << errno << "): " << strerror(errno) << std::endl;
            throw exception();
        }
        std::vector<char> msg(buff, buff+n);
        std::cout << "[TRACE] server_daemon: message received (" << n << " bytes)" << std::endl;
        auto v = handle_msg(msg);
        switch (v) {
            case server_reply:
                sendto(sockfd, msg.data(), msg.size(), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
                std::cout << "[TRACE] reply sent: " << msg.size() << " bytes" << std::endl;
                break;
            case server_noreply:
                std::cout << "[TRACE] no reply sent" << std::endl;
                break;
            case server_teardown:
                running = false;
                break;
        }
    }
    std::cout << "[INFO] server_daemon: at exit" << std::endl;
}

daemon::daemon(const char *addr, uint16_t port) {
    // Creating socket file descriptor
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        std::cout << "[CRITICAL] socket creation failed (" << errno <<"): " << strerror(errno) << std::endl;
        throw exception();
    }

    // Filling server information
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(addr);
}

int daemon::send_msg(std::vector<char> &msg, bool needs_reply) {
    auto ret = sendto(sockfd, msg.data(), msg.size(), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (ret < 0) {
        std::cout << "[ERROR] send failed: " << strerror(errno) << std::endl;
        return -1;
    }
    if (needs_reply) {
        char buff[DAEMON_BUFSIZE];
        socklen_t len;
        auto n = recvfrom(sockfd, buff, DAEMON_BUFSIZE, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
        msg.assign(buff, buff+n);
        std::cout << "[TRACE] received answer (" << n << " bytes)" << std::endl;
        return n;
    }
    return 0;
}

void daemon::client() {

}

#define test_me
#ifdef test_me

#include <sstream>
class server : public daemon {
public:
    explicit server(uint16_t port)
    : daemon(port)
    {
        std::cout << "[INFO] server constructor" << std::endl;
    }

protected:
    void init_server() override {
        std::cout << "[INFO] init_server" << std::endl;
    }

    server_cmd handle_msg(std::vector<char> &msg) override {
        std::string s(msg.begin(), msg.end());
        std::cout << "[INFO] received '" << s << "'" << std::endl;
        return (s == "END")? server_teardown: server_noreply;
    };

public:
};

class client : public daemon {
public:
    explicit client(const char *addr, uint16_t port)
            : daemon(addr, port)
    {}

protected:
    server_cmd handle_msg(std::vector<char> &msg) override {
        std::cout << "[CRITICAL] client handle_msg() called" << std::endl;
        return server_teardown;
    };

public:
};

int main() {
    try {
        server server(12345);
    } catch (daemon::exception &e) {
        std::cout << "[INFO] daemon already running" << std::endl;
    }
    client client("127.0.0.1", 12345);
    for (auto i : {1, 2, 3}) {
        std::ostringstream msg;
        msg << "message No." << i << " sent";
        auto str = msg.str();
        std::vector<char> vec(str.begin(), str.end());
        client.send_msg(vec);
    }
    std::vector<char> v({'E', 'N', 'D'});
    client.send_msg(v);
}
#endif

问题确认与解决方案

问题原因

你的怀疑完全正确。C++子类对象的构造顺序是先执行基类构造函数,再执行子类构造函数。当你在基类daemon(uint16_t port)构造函数中调用fork()时,子类的构造逻辑还未开始执行,此时对象的虚函数表仍是基类版本。子进程会复制父进程的内存空间,因此子进程中的对象始终处于基类未完全构造的状态,后续调用init_server()、handle_msg()等虚函数时,只会执行基类的默认实现。

解决方案

方案1:将fork()逻辑移至子类构造完成后

把启动守护进程的代码从基类构造函数中移出,改为在子类构造完成后调用:

  1. 给基类添加启动方法:
class daemon {
    // ... 原有成员 ...
public:
    // ... 原有构造函数 ...
    void start_daemon() {
        auto pid = fork();
        if (pid < 0) {
            std::cout << "[CRITICAL] fork failed: " << strerror(errno) << std::endl;
        } else if (pid == 0) {
            init_logger();
            server();
            exit(EXIT_SUCCESS);
        }
        sleep(1);
    }
};
  1. 修改子类构造函数,在末尾调用启动方法:
class server : public daemon {
public:
    explicit server(uint16_t port)
    : daemon(port)
    {
        std::cout << "[INFO] server constructor" << std::endl;
        start_daemon(); // 子类完全构造后启动守护进程
    }
    // ... 原有成员 ...
};

注意:需确保父进程不会关闭socket描述符,子进程才能正常继承使用。

方案2:使用静态工厂方法创建子类

通过静态方法完成子类对象的创建和守护进程启动,确保子类完全构造后再执行fork():

class server : public daemon {
public:
    static void create_and_start(uint16_t port) {
        server srv(port); // 子类完全构造
        srv.start_daemon();
    }
    // ... 原有成员 ...
};

// main函数中调用
server::create_and_start(12345);

子进程输出修复

你的init_logger()中,std::ofstream out("out.txt")是局部变量,函数结束后会被销毁,导致std::cout指向无效缓冲区。需将输出流改为静态成员:

class daemon {
private:
    static std::ofstream log_out; // 静态成员
    // ...
    void init_logger(const char *fn="log.txt") {
        log_out.open(fn);
        std::cout.rdbuf(log_out.rdbuf());
    }
};

// 在实现文件中初始化静态成员
std::ofstream daemon::log_out;

另外,::daemon(1,1)会将标准输入输出重定向到/dev/null,需在调用该函数前完成输出重定向,或者自行实现守护进程化逻辑(如创建会话、切换工作目录等)。


内容的提问来源于stack exchange,提问作者ZioByte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:15:55