簡體   English   中英

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

[英]Bad interaction between fork() and inheritance

我有以下代碼,不是最小的,但不清楚如何在不掩蓋效果的情況下進一步減少它。

我有一個 class 代表服務器和客戶端,但我不認為這是問題所在。

出現問題是因為如果一台服務器已經在運行,我會嘗試檢查服務器構造函數,如果沒有,我會分叉並分離(實際上是“守護進程”)服務器。

這似乎可行,但生成的服務器始終是基礎 class,即使我正在嘗試啟動一個孩子。

實際上 base class 是“空的”,它應該被子類化以覆蓋消息的實際處理......這根本不起作用。

我懷疑在“fork()”時孩子 class 沒有設置,我分叉了父母,但我想要確認,如果可能的話,一個解決方法。

注意:在子(服務器)上打印不(當前)工作我試圖從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

這與fork()無關。 如果每次隨機數選擇服務器代碼路徑時都將fork()替換為隨機數生成器,您將獲得相同的結果。

class server : public daemon 

在 C++ 中,當您構造一個繼承自基數 class 的 class 時,首先構造基數 class。 直到基礎 class 完成構造派生 class 才存在。 這是 C++ 的基礎,這就是 C++ 的工作原理。

在基類的構造函數中:

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();

這是基礎 class,子 class在基礎構造函數返回之前不會被構造。 server() function 期望調用在子 class 中覆蓋的虛擬方法。但是子 class 不存在。 從某種意義上說,它尚未誕生。

這就是 C++ 的基本工作原理。 您需要相應地更改類的設計,以達到預期的效果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM