diff --git a/webserv/client/Client.cpp b/webserv/client/Client.cpp index efd77c4..49eda00 100644 --- a/webserv/client/Client.cpp +++ b/webserv/client/Client.cpp @@ -1,22 +1,44 @@ +#include "webserv/socket/Socket.hpp" #include #include -Client::Client(int client_fd, Server &server, const ServerConfig &server_config) - : client_fd(client_fd), server(std::ref(server)), server_config(std::cref(server_config)) +Client::Client(std::unique_ptr socket, Server &server, const ServerConfig &server_config) + : client_socket_(std::move(socket)), server(std::ref(server)), server_config(std::cref(server_config)) { } -void Client::request(const std::string &req) +Client::~Client(){ + std::cout << "Client destructor called for fd: " << client_socket_->getFd() << '\n'; + server.removeFromEpoll(*client_socket_); +}; + +void Client::request() { + char buffer[1024] = {}; + ssize_t bytesRead = client_socket_->recv(buffer, sizeof(buffer) - 1); + if (bytesRead < 0) + { + perror("Read error"); + return; + } + if (bytesRead == 0) + { + std::cout << "Client disconnected, fd: " << client_socket_->getFd() << '\n'; + return; + } + + buffer[bytesRead] = '\0'; // Null-terminate the buffer + std::cout << "Received request:\n" << buffer << '\n'; + // Handle the request (placeholder implementation) - std::cout << "Client received request: " << req << '\n'; - server.get().responseReady(client_fd); + server.responseReady(client_socket_->getFd()); } + std::string Client::getResponse() const { std::string response = "HTTP/1.1 200 OK\r\nContent-Length: 32\r\n\r\nHello, World!"; - response += " Server port " + std::to_string(server_config.get().getPort()) + "\r\n"; + response += " Server port " + std::to_string(server_config.getPort()) + "\r\n"; std::cout << response << '\n'; return response; } \ No newline at end of file diff --git a/webserv/client/Client.hpp b/webserv/client/Client.hpp index 66d6414..a5e5286 100644 --- a/webserv/client/Client.hpp +++ b/webserv/client/Client.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include "webserv/socket/Socket.hpp" +#include #include #include @@ -9,13 +10,21 @@ class Server; class Client { public: - Client(int client_fd, Server &server, const ServerConfig &server_config); - void request(const std::string &req); - std::string getResponse() const; + Client(std::unique_ptr socket, Server &server, const ServerConfig &server_config); + + Client(const Client &other) = delete; // Disable copy constructor + Client &operator=(const Client &other) = delete; // Disable copy assignment + Client(Client &&other) noexcept = delete; // Move constructor + Client &operator=(Client &&other) noexcept = delete; // Move assignment + + ~Client(); + + void request(); + [[nodiscard]] std::string getResponse() const; private: - int client_fd; - std::reference_wrapper server; - std::reference_wrapper server_config; + std::unique_ptr client_socket_; + const Server &server; + const ServerConfig &server_config; }; \ No newline at end of file diff --git a/webserv/server/Server.cpp b/webserv/server/Server.cpp index 223e6dc..d790247 100644 --- a/webserv/server/Server.cpp +++ b/webserv/server/Server.cpp @@ -1,112 +1,167 @@ +#include "webserv/socket/Socket.hpp" #include // For std::find #include // For inet_addr #include // For exit() #include // For memset #include // For fcntl()" #include +#include #include // For sockaddr_in -#include // For std::ranges::find #include #include // For socket functions #include // For close() #include #include -Server::Server(const ConfigManager &configManager) : _epoll_fd(-1), _configManager(configManager) +Server::Server(const ConfigManager &configManager) : epoll_fd_(epoll_create1(0)), configManager_(configManager) { const auto &serverConfigs = configManager.getServerConfigs(); if (serverConfigs.empty()) { throw std::runtime_error("No server configurations available."); } + if (epoll_fd_ == -1) + { + throw std::runtime_error("epoll_create1 failed"); + } } -int createServerSocket(const std::string &host, int port) +Server::~Server() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd == -1) + if (epoll_fd_ != -1) { - perror("Socket creation failed"); - exit(EXIT_FAILURE); + close(epoll_fd_); } - - int opt = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) - { - perror("setsockopt"); - close(server_fd); - exit(EXIT_FAILURE); - } - - struct sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_addr.s_addr = inet_addr(host.c_str()); - address.sin_port = htons(port); - - if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT - { - perror("Bind failed"); - close(server_fd); - exit(EXIT_FAILURE); - } - - if (listen(server_fd, SOMAXCONN) < 0) - { - perror("Listen failed"); - close(server_fd); - exit(EXIT_FAILURE); - } - - fcntl(server_fd, F_SETFL, O_NONBLOCK); - std::cout << "Server listening on " << host << ":" << port << "...\n"; - return server_fd; } + void Server::start() { std::cout << "Starting servers...\n"; // 1. Load server configurations - for (const auto &config : _configManager.get().getServerConfigs()) + for (const auto &config : configManager_.getServerConfigs()) { - int server_fd = createServerSocket(config.getHost(), config.getPort()); - _fdToConfig.insert({server_fd, std::cref(config)}); + setupServerSocket(config); } - if (_fdToConfig.empty()) + if (fdToConfig_.empty()) { throw std::runtime_error("No server sockets created."); } - // 5. Set up epoll - int epoll_fd = epoll_create1(0); - if (epoll_fd == -1) - { - perror("epoll_create1 failed"); - // TODO: Close all server FDs - // for (int server_fd : _server_fds) - // { - // close(server_fd); - // } - } - - for (const auto &pair : _fdToConfig) - { - int server_fd = pair.first; - struct epoll_event event{}; - event.events = EPOLLIN; // Interested in read events - event.data.fd = server_fd; - if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) - { - perror("epoll_ctl failed"); - // TODO close fds - close(epoll_fd); - // throw exception - } - // _server_fds.push_back(server_fd); - } - _epoll_fd = epoll_fd; eventLoop(); } +void Server::addToEpoll(const Socket &socket, uint32_t events) const +{ + int fd = socket.getFd(); + struct epoll_event event{}; + event.events = events; + event.data.fd = fd; + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event) == -1) + { + throw std::runtime_error("epoll_ctl ADD failed"); + } +} + +void Server::removeFromEpoll(const Socket &socket) const +{ + int filedes = socket.getFd(); + if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, filedes, nullptr) == -1) + { + throw std::runtime_error("epoll_ctl DEL failed"); + } +} + +void Server::setupServerSocket(const ServerConfig &config) +{ + try + { + std::unique_ptr serverSocket = std::make_unique(); + serverSocket->bind(config.getHost(), config.getPort()); + serverSocket->listen(SOMAXCONN); + int server_fd = serverSocket->getFd(); + + addToEpoll(*serverSocket, EPOLLIN); + + listeners_.push_back(std::move(serverSocket)); + fdToConfig_.insert({server_fd, std::cref(config)}); + std::cout << "Server listening on " << config.getHost() << ":" << config.getPort() << "...\n"; + } + catch (const std::exception &e) + { + std::cerr << "Error setting up server socket: " << e.what() << '\n'; + } +} + +void Server::handleConnection(struct epoll_event *event) +{ + Socket &listener = getListener(event->data.fd); + std::unique_ptr clientSocket = listener.accept(); + addToEpoll(*clientSocket, EPOLLIN | EPOLLET); + clients_.insert( + {clientSocket->getFd(), std::make_unique(std::move(clientSocket), *this, getConfig(listener))}); + // clients_.insert({clientSocket->getFd(), Client{std::move(clientSocket), *this, getConfig(listener)}}); +} + +Socket &Server::getListener(int fd) const +{ + for (const auto &listener : listeners_) + { + if (listener->getFd() == fd) + { + return *listener; + } + } + throw std::runtime_error("Listener not found for fd: " + std::to_string(fd)); +} + +Client &Server::getClient(int fd) const +{ + auto it = clients_.find(fd); + if (it != clients_.end()) + { + return *(it->second); + } + throw std::runtime_error("Client not found for fd: " + std::to_string(fd)); +} + +const ServerConfig &Server::getConfig(const Socket &socket) const +{ + return getConfig(socket.getFd()); +} + +const ServerConfig &Server::getConfig(int fd) const +{ + auto it = fdToConfig_.find(fd); + if (it != fdToConfig_.end()) + { + return (it->second.get()); + } + throw std::runtime_error("Config not found for fd: " + std::to_string(fd)); +} + +void Server::handleRequest(struct epoll_event *event) +{ + std::cout << "Handling request...\n"; + + int client_fd = event->data.fd; + + Client &client = getClient(client_fd); + client.request(); +} + +void Server::responseReady(int client_fd) const +{ + std::cout << "Response ready for client fd: " << client_fd << '\n'; + struct epoll_event ev{}; + ev.events = EPOLLOUT | EPOLLET; + ev.data.fd = client_fd; + if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, client_fd, &ev) == -1) + { + throw std::runtime_error("epoll_ctl MOD failed"); + } +} + void Server::eventLoop() { // Placeholder for the event loop logic @@ -115,11 +170,10 @@ void Server::eventLoop() struct epoll_event events[MAX_EVENTS]; // NOLINT while (true) { - int nfds = epoll_wait(_epoll_fd, events, MAX_EVENTS, -1); // NOLINT + int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); // NOLINT if (nfds == -1) { - perror("epoll_wait failed"); - break; + throw std::runtime_error("epoll_wait failed"); } for (int i = 0; i < nfds; ++i) { @@ -127,92 +181,235 @@ void Server::eventLoop() if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0) { std::cerr << "Epoll error on fd " << event.data.fd << '\n'; + removeFromEpoll(getListener(event.data.fd)); close(event.data.fd); } - else if (_fdToConfig.contains(event.data.fd)) + else if (fdToConfig_.contains(event.data.fd)) { - handleConnection(_epoll_fd, &event); + handleConnection(&event); } else if ((event.events & EPOLLIN) > 0) { - handleRequest(_epoll_fd, &event); + handleRequest(&event); } else if ((event.events & EPOLLOUT) > 0) { std::cout << "Attempting to send data to fd: " << event.data.fd << '\n'; - std::string response = clients.at(event.data.fd).getResponse(); + Client &client = getClient(event.data.fd); + std::string response = client.getResponse(); const char *httpResponse = response.c_str(); ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0); if (bytesSent < 0) { perror("Send error"); } - clients.erase(event.data.fd); - close(event.data.fd); // Close after sending response + clients_.erase(event.data.fd); } } } } -void Server::handleRequest(int epoll_fd, struct epoll_event *event) -{ - std::cout << "Handling request...\n"; +// Server::Server(const ConfigManager &configManager) : _epoll_fd(-1), _configManager(configManager) +// { +// const auto &serverConfigs = configManager.getServerConfigs(); +// if (serverConfigs.empty()) +// { +// throw std::runtime_error("No server configurations available."); +// } +// } - int client_fd = event->data.fd; - std::cout << "client fd: " << client_fd << '\n'; - char buffer[1024] = {}; - ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1); - if (bytesRead < 0) - { - perror("Read error"); - close(client_fd); - return; - } - if (bytesRead == 0) - { - std::cout << "Client disconnected, fd: " << client_fd << '\n'; - close(client_fd); - return; - } +// int createServerSocket(const std::string &host, int port) +// { +// int server_fd = socket(AF_INET, SOCK_STREAM, 0); +// if (server_fd == -1) +// { +// perror("Socket creation failed"); +// exit(EXIT_FAILURE); +// } - buffer[bytesRead] = '\0'; // Null-terminate the buffer - std::cout << "Received request:\n" << buffer << '\n'; +// int opt = 1; +// if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) +// { +// perror("setsockopt"); +// close(server_fd); +// exit(EXIT_FAILURE); +// } - // clients[client_fd] = Client(client_fd, *this, ServerConfig); - clients.at(client_fd).request(buffer); -} +// struct sockaddr_in address{}; +// address.sin_family = AF_INET; +// address.sin_addr.s_addr = inet_addr(host.c_str()); +// address.sin_port = htons(port); -void Server::responseReady(int client_fd) -{ - std::cout << "Response ready for client fd: " << client_fd << '\n'; - struct epoll_event ev; - ev.events = EPOLLOUT | EPOLLET; - ev.data.fd = client_fd; - epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, client_fd, &ev); -} +// if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT +// { +// perror("Bind failed"); +// close(server_fd); +// exit(EXIT_FAILURE); +// } -void Server::handleConnection(int epoll_fd, struct epoll_event *event) -{ - int client_fd = accept(event->data.fd, nullptr, nullptr); - std::cout << "connection " << client_fd << '\n'; - fcntl(client_fd, F_SETFL, O_NONBLOCK); - if (client_fd == -1) - { - perror("Accept failed"); - return; - } - std::cout << "New connection accepted, fd: " << client_fd << '\n'; - struct epoll_event client_event; - client_event.events = EPOLLIN | EPOLLET; - client_event.data.fd = client_fd; - if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event) == -1) - { - perror("epoll_ctl failed"); - close(client_fd); - close(epoll_fd); - // throw exception - } +// if (listen(server_fd, SOMAXCONN) < 0) +// { +// perror("Listen failed"); +// close(server_fd); +// exit(EXIT_FAILURE); +// } - std::cout << "client fd " << client_fd << ", server fd " << event->data.fd << '\n'; - clients.insert({client_fd, Client(client_fd, *this, _fdToConfig.at(event->data.fd))}); -} \ No newline at end of file +// fcntl(server_fd, F_SETFL, O_NONBLOCK); +// std::cout << "Server listening on " << host << ":" << port << "...\n"; +// return server_fd; +// } +// void Server::start() +// { +// std::cout << "Starting servers...\n"; +// // 1. Load server configurations + +// for (const auto &config : _configManager.get().getServerConfigs()) +// { +// int server_fd = createServerSocket(config.getHost(), config.getPort()); +// _fdToConfig.insert({server_fd, std::cref(config)}); +// } +// if (_fdToConfig.empty()) +// { +// throw std::runtime_error("No server sockets created."); +// } + +// // 5. Set up epoll +// int epoll_fd = epoll_create1(0); +// if (epoll_fd == -1) +// { +// perror("epoll_create1 failed"); +// // TODO: Close all server FDs +// // for (int server_fd : _server_fds) +// // { +// // close(server_fd); +// // } +// } + +// for (const auto &pair : _fdToConfig) +// { +// int server_fd = pair.first; +// struct epoll_event event{}; +// event.events = EPOLLIN; // Interested in read events +// event.data.fd = server_fd; +// if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) +// { +// perror("epoll_ctl failed"); +// // TODO close fds +// close(epoll_fd); +// // throw exception +// } +// // _server_fds.push_back(server_fd); +// } +// _epoll_fd = epoll_fd; +// eventLoop(); +// } + +// void Server::eventLoop() +// { +// // Placeholder for the event loop logic +// std::cout << "Entering event loop...\n"; +// const int MAX_EVENTS = 10; +// struct epoll_event events[MAX_EVENTS]; // NOLINT +// while (true) +// { +// int nfds = epoll_wait(_epoll_fd, events, MAX_EVENTS, -1); // NOLINT +// if (nfds == -1) +// { +// perror("epoll_wait failed"); +// break; +// } +// for (int i = 0; i < nfds; ++i) +// { +// epoll_event &event = events[i]; +// if ((event.events & EPOLLERR) > 0 || (event.events & EPOLLHUP) > 0) +// { +// std::cerr << "Epoll error on fd " << event.data.fd << '\n'; +// close(event.data.fd); +// } +// else if (_fdToConfig.contains(event.data.fd)) +// { +// handleConnection(_epoll_fd, &event); +// } +// else if ((event.events & EPOLLIN) > 0) +// { +// handleRequest(_epoll_fd, &event); +// } +// else if ((event.events & EPOLLOUT) > 0) +// { +// std::cout << "Attempting to send data to fd: " << event.data.fd << '\n'; +// std::string response = clients.at(event.data.fd).getResponse(); +// const char *httpResponse = response.c_str(); +// ssize_t bytesSent = send(event.data.fd, httpResponse, strlen(httpResponse), 0); +// if (bytesSent < 0) +// { +// perror("Send error"); +// } +// clients.erase(event.data.fd); +// close(event.data.fd); // Close after sending response +// } +// } +// } +// } + +// void Server::handleRequest(int epoll_fd, struct epoll_event *event) +// { +// std::cout << "Handling request...\n"; + +// int client_fd = event->data.fd; +// std::cout << "client fd: " << client_fd << '\n'; +// char buffer[1024] = {}; +// ssize_t bytesRead = read(client_fd, buffer, sizeof(buffer) - 1); +// if (bytesRead < 0) +// { +// perror("Read error"); +// close(client_fd); +// return; +// } +// if (bytesRead == 0) +// { +// std::cout << "Client disconnected, fd: " << client_fd << '\n'; +// close(client_fd); +// return; +// } + +// buffer[bytesRead] = '\0'; // Null-terminate the buffer +// std::cout << "Received request:\n" << buffer << '\n'; + +// // clients[client_fd] = Client(client_fd, *this, ServerConfig); +// clients.at(client_fd).request(buffer); +// } + +// void Server::responseReady(int client_fd) +// { +// std::cout << "Response ready for client fd: " << client_fd << '\n'; +// struct epoll_event ev; +// ev.events = EPOLLOUT | EPOLLET; +// ev.data.fd = client_fd; +// epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, client_fd, &ev); +// } + +// void Server::handleConnection(int epoll_fd, struct epoll_event *event) +// { +// int client_fd = accept(event->data.fd, nullptr, nullptr); +// std::cout << "connection " << client_fd << '\n'; +// fcntl(client_fd, F_SETFL, O_NONBLOCK); +// if (client_fd == -1) +// { +// perror("Accept failed"); +// return; +// } +// std::cout << "New connection accepted, fd: " << client_fd << '\n'; +// struct epoll_event client_event; +// client_event.events = EPOLLIN | EPOLLET; +// client_event.data.fd = client_fd; +// if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_event) == -1) +// { +// perror("epoll_ctl failed"); +// close(client_fd); +// close(epoll_fd); +// // throw exception +// } + +// std::cout << "client fd " << client_fd << ", server fd " << event->data.fd << '\n'; +// clients.insert({client_fd, Client(client_fd, *this, _fdToConfig.at(event->data.fd))}); +// } \ No newline at end of file diff --git a/webserv/server/Server.hpp b/webserv/server/Server.hpp index 94d9da4..839505c 100644 --- a/webserv/server/Server.hpp +++ b/webserv/server/Server.hpp @@ -1,30 +1,49 @@ #pragma once +#include "webserv/config/ServerConfig.hpp" #include -#include +#include +#include #include +#include +#include class Client; class Server { public: + Server() = delete; Server(const ConfigManager &configManager); + Server(const Server &other) = delete; // Disable copy constructor + Server &operator=(const Server &other) = delete; // Disable copy assignment + Server(Server &&other) noexcept = delete; // Move constructor + Server &operator=(Server &&other) noexcept = delete; // Move assignment + // The constructor must initialize the reference member 'configManager' // Implementation should be in the .cpp file using an initializer list + ~Server(); + void start(); - void setupServerSocket(ServerConfig &config); - void handleConnection(int epoll_fd, struct epoll_event *event); - void handleRequest(int epoll_fd, struct epoll_event *event); - void responseReady(int client_fd); + void addToEpoll(const Socket &socket, uint32_t events) const; + void removeFromEpoll(const Socket &socket) const ; + void setupServerSocket(const ServerConfig &config); + void handleConnection(struct epoll_event *event); + void handleRequest(struct epoll_event *event); + void responseReady(int client_fd) const; void eventLoop(); + Socket &getListener(int fd) const; + Client &getClient(int fd) const; + const ServerConfig &getConfig(int fd) const; + const ServerConfig &getConfig(const Socket &socket) const; + private: - int _epoll_fd; - std::reference_wrapper _configManager; - std::map> _fdToConfig; - std::vector _server_fds; - std::map clients; + int epoll_fd_; + const ConfigManager &configManager_; + std::vector> listeners_; + std::unordered_map> fdToConfig_; + std::unordered_map> clients_; }; diff --git a/webserv/socket/Socket.cpp b/webserv/socket/Socket.cpp index a8c13e9..f6ac786 100644 --- a/webserv/socket/Socket.cpp +++ b/webserv/socket/Socket.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -21,6 +22,7 @@ Socket::Socket() : _fd(socket(AF_INET, SOCK_STREAM, 0)) _fd = -1; throw std::runtime_error("setsockopt failed"); } + setNonBlocking(); } Socket::Socket(int fd) : _fd(fd) // NOLINT readability-identifier-naming @@ -29,6 +31,7 @@ Socket::Socket(int fd) : _fd(fd) // NOLINT readability-identifier-naming { throw std::runtime_error("Invalid file descriptor"); } + setNonBlocking(); } Socket::~Socket() @@ -54,20 +57,20 @@ void Socket::bind(const std::string &host, const int port) const address.sin_addr.s_addr = inet_addr(host.c_str()); address.sin_port = htons(port); - if (::bind(_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT cppcoreguidelines-pro-type-cstyle-cast + if (::bind(_fd, (struct sockaddr *)&address, sizeof(address)) < 0) // NOLINT(cppcoreguidelines-pro-type-cstyle-cast { throw std::runtime_error("Bind failed"); } } -Socket Socket::accept() const +std::unique_ptr Socket::accept() const { int client_fd = ::accept(_fd, nullptr, nullptr); if (client_fd < 0) { throw std::runtime_error("Accept failed"); } - return {client_fd}; + return std::make_unique(client_fd); } ssize_t Socket::recv(void *buf, size_t len) const @@ -86,4 +89,9 @@ void Socket::setNonBlocking() const { throw std::runtime_error("Failed to set non-blocking mode"); } +} + +int Socket::getFd() const +{ + return _fd; } \ No newline at end of file diff --git a/webserv/socket/Socket.hpp b/webserv/socket/Socket.hpp index 3100571..3634b70 100644 --- a/webserv/socket/Socket.hpp +++ b/webserv/socket/Socket.hpp @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include class Socket { @@ -9,20 +10,20 @@ class Socket Socket(); Socket(int fd); // NOLINT readability-identifier-naming - Socket(const Socket &other) = delete; // Disable copy constructor - Socket &operator=(const Socket &other) = delete; // Disable copy assignment - Socket(Socket &&other) noexcept; // Move constructor - Socket &operator=(Socket &&other) noexcept; // Move assignment + Socket(const Socket &other) = delete; // Disable copy constructor + Socket &operator=(const Socket &other) = delete; // Disable copy assignment + Socket(Socket &&other) noexcept = default; // Move constructor + Socket &operator=(Socket &&other) noexcept = default; // Move assignment ~Socket(); void listen(int backlog) const; void bind(const std::string &host, const int port) const; - [[nodiscard]] Socket accept() const; + [[nodiscard]] std::unique_ptr accept() const; ssize_t recv(void *buf, size_t len) const; ssize_t send(const void *buf, size_t len) const; void setNonBlocking() const; - [[nodiscard]] int getFd() const; + [[nodiscard]] int getFd() const; private: int _fd;