简体   繁体   English

fork() 和 inheritance 之间的交互不良

[英]Bad interaction between fork() and inheritance

I have the following code, not minimal, but it's unclear how to further reduce it without masking the effect.我有以下代码,不是最小的,但不清楚如何在不掩盖效果的情况下进一步减少它。

I have a single class representing both server and client, but I don't think this is the problem.我有一个 class 代表服务器和客户端,但我不认为这是问题所在。

Problem arises because I try to check in server constructor if one server is already running and, if not, I fork and detach (actually "daemonize") the server.出现问题是因为如果一台服务器已经在运行,我会尝试检查服务器构造函数,如果没有,我会分叉并分离(实际上是“守护进程”)服务器。

This seems to work but spawned server is always the base class, even if I am trying to start a child.这似乎可行,但生成的服务器始终是基础 class,即使我正在尝试启动一个孩子。

Actually base class is "empty" and it's supposed to be subclassed overriding the actual handing of messages... which doesn't work at all.实际上 base class 是“空的”,它应该被子类化以覆盖消息的实际处理......这根本不起作用。

I suspect at time of "fork()" child class is not set up and I fork the parent, but I would like confirmation and, if possible, a workaround.我怀疑在“fork()”时孩子 class 没有设置,我分叉了父母,但我想要确认,如果可能的话,一个解决方法。

Note: printing on the child (server) does not (currently) work I tried to convert to standard iostream from spdlog , but I must have goofed somewhere.注意:在子(服务器)上打印不(当前)工作我试图从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

This has nothing to do with fork() .这与fork()无关。 If you replace fork() with a random number generator every time the random number picked the server code path you will get the same results.如果每次随机数选择服务器代码路径时都将fork()替换为随机数生成器,您将获得相同的结果。

class server : public daemon 

In C++ when you construct a class that inherits from a base class, the base class gets constructed first.在 C++ 中,当您构造一个继承自基数 class 的 class 时,首先构造基数 class。 Until the base class finishes constructing the derived class does not exist.直到基础 class 完成构造派生 class 才存在。 This is fundamental to C++, that's how C++ works.这是 C++ 的基础,这就是 C++ 的工作原理。

In the base class's constructor:在基类的构造函数中:

daemon::daemon(uint16_t port) {

// ...

    auto pid = fork();
    if (pid < 0) {
        std::cout << "[CRITICAL] fork failed: " << strerror(errno) << std::endl;
    } else if (pid == 0) {
        init_logger();
        // child; daemonize
        server();

This is the base class, the child class does not get constructed until the base constructor returns .这是基础 class,子 class在基础构造函数返回之前不会被构造。 The server() function expects to invoke virtual methods that are overridden in the child class. But the child class does not exist. server() function 期望调用在子 class 中覆盖的虚拟方法。但是子 class 不存在。 It is yet to be born, in a manner of speaking.从某种意义上说,它尚未诞生。

This is just how C++ works fundamentally.这就是 C++ 的基本工作原理。 You'll need to change the design of your classes, accordingly, in order to achieve the desired results.您需要相应地更改类的设计,以达到预期的效果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM