[英]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.