diff --git a/CMakeLists.txt b/CMakeLists.txt index 64a35a8..53805bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ FetchContent_MakeAvailable(googletest) # -add_executable(playground src/main.cpp src/tcp.cpp) +add_executable(playground src/main.cpp src/tcp_server.cpp src/tcp_client.cpp) target_link_libraries(playground PRIVATE spdlog::spdlog) @@ -56,21 +56,13 @@ target_link_libraries(playground PRIVATE spdlog::spdlog) enable_testing() -add_executable( - tcp_test +add_executable(tcp_test tests/tcp.cpp - src/tcp.cpp -) -target_link_libraries( - tcp_test - spdlog::spdlog - GTest::gtest_main -) -target_include_directories( - tcp_test - PRIVATE - src + src/tcp_server.cpp + src/tcp_client.cpp ) +target_link_libraries(tcp_test spdlog::spdlog GTest::gtest_main) +target_include_directories(tcp_test PRIVATE src) include(GoogleTest) gtest_discover_tests(tcp_test) diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..56fc4ca --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,48 @@ +#pragma once + + +// STL includes +#include + +// C stdlib includes +#include +#include + +// Library includes +#include + + +namespace tcp { + + +inline std::expected resolve_remote_address(const char* host, + uint16_t port) { + hostent* server = gethostbyname(host); + if (server == nullptr) { + spdlog::error("tcp: get_host_by_name() failed with h_errno={}", + h_errno); + return std::unexpected{h_errno}; + } + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_port = htons(port); + bcopy((char*)server->h_addr, (char*)&address.sin_addr.s_addr, + server->h_length); + + return address; +} + +inline sockaddr_in get_local_address(uint16_t port) { + sockaddr_in address{}; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_family = AF_INET; + address.sin_port = htons(port); + return address; +} + + +using HostString = std::array; + + +} // namespace tcp diff --git a/src/main.cpp b/src/main.cpp index a0a6e02..e0f3dd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include // Project includes -#include "tcp.hpp" +#include "tcp_server.hpp" int main() { diff --git a/src/tcp.cpp b/src/tcp.cpp deleted file mode 100644 index b729a8a..0000000 --- a/src/tcp.cpp +++ /dev/null @@ -1,449 +0,0 @@ -#include "tcp.hpp" - -// C stdlib includes -#include -#include -#include - -// Library includes -#include - - -namespace { - - -std::expected resolve_remote_address(const char* host, - uint16_t port) { - hostent* server = gethostbyname(host); - if (server == nullptr) { - spdlog::error("tcp: get_host_by_name() failed with h_errno={}", - h_errno); - return std::unexpected{h_errno}; - } - - sockaddr_in address{}; - address.sin_family = AF_INET; - address.sin_port = htons(port); - bcopy((char*)server->h_addr, (char*)&address.sin_addr.s_addr, - server->h_length); - - return address; -} - -inline sockaddr_in get_local_address(uint16_t port) { - sockaddr_in address{}; - address.sin_addr.s_addr = htonl(INADDR_ANY); - address.sin_family = AF_INET; - address.sin_port = htons(port); - return address; -} - - -} // namespace - - -namespace tcp { - - -// -// -// Non blocking server -// -// - - -NonBlockingServer::~NonBlockingServer() { - close_client_socket(); - close_server_socket(); -} - -/// -/// @details Calling this repeatedly closes the previous socket and creates a -/// new one. -/// -std::expected NonBlockingServer::start_listening(uint16_t port) { - /// Create socket - - close_server_socket(); - if (auto res = create_server_socket(); !res) { - return std::unexpected{res.error()}; - } - - /// Bin socket and start listening - - const struct sockaddr_in serverAddress = get_local_address(port); - - if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress, - sizeof(serverAddress)) != 0) { - spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}", - errno); - close_server_socket(); - return std::unexpected{errno}; - }; - - if (::listen(m_serverSocket, 1) != 0) { - spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}", - errno); - close_server_socket(); - return std::unexpected{errno}; - } - - return {}; -} - -void NonBlockingServer::stop_listening() { - close_server_socket(); -} - -bool NonBlockingServer::next_client_available() { - int ret = poll(&m_serverPfdIn, 1, 0); - if (ret < 0) { - spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", - errno); - return false; - } - - return (m_serverPfdIn.revents & POLLIN); -} - -[[nodiscard]] std::expected NonBlockingServer::accept_next_client() { - disconnect_from_client(); - - socklen_t clientAddrLen = sizeof(m_clientAddress); - - int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress, - &clientAddrLen); - if (result < 0) return std::unexpected{errno}; - - m_clientSocket = result; - m_clientPfdIn.fd = m_clientSocket; - - return {}; -} - -void NonBlockingServer::disconnect_from_client() { - close_client_socket(); -} - -[[nodiscard]] std::expected, int> -NonBlockingServer::get_client_ip_as_string() { - std::array addrStr; - - if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr, - addrStr.data(), addrStr.size()) == nullptr) { - spdlog::error( - "tcp::NonBlockingServer: inet_ntop() failed with errno={}", errno); - return std::unexpected{errno}; - } - - // if (inet_ntoa_r(((struct sockaddr_in*)&m_clientAddress)->sin_addr, - // addrStr.data(), addrStr.size()) == nullptr) { - // return std::unexpected{errno}; - // } - - return addrStr; -} - -bool NonBlockingServer::data_available() { - int ret = poll(&m_clientPfdIn, 1, 0); - if (ret < 0) { - spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", - errno); - return false; - } - - return (m_clientPfdIn.revents & POLLIN); -} - -[[nodiscard]] std::expected -NonBlockingServer::send(std::span data) { - const int bytesWritten = - ::send(m_clientSocket, (const char*)data.data(), data.size(), 0); - - if (bytesWritten <= 0) { - spdlog::error("tcp::NonBlockingServer: send() failed with errno={}", - errno); - return std::unexpected{errno}; - } - - return bytesWritten; -} - -[[nodiscard]] std::expected -NonBlockingServer::recv(std::span buffer) { - const int bytesReceived = - ::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0); - - if (bytesReceived < 0) { - spdlog::error("tcp::NonBlockingServer: recv() failed with errno={}", - errno); - return std::unexpected{errno}; - } - return {bytesReceived}; -} - -[[nodiscard]] std::expected -NonBlockingServer::create_server_socket() { - m_serverSocket = socket(AF_INET, SOCK_STREAM, 0); - if (m_serverSocket < 1) { - spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}", - errno); - close_server_socket(); - return std::unexpected{errno}; - } - - m_serverPfdIn.fd = m_serverSocket; - - auto currentFlags = fcntl(m_serverSocket, F_GETFL, 0); - if (currentFlags == -1) { - spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", - errno); - close_server_socket(); - return std::unexpected{errno}; - } - - if (fcntl(m_serverSocket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { - spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", - errno); - close_server_socket(); - return std::unexpected{errno}; - } - - int flag = 1; - if (::setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &flag, - sizeof(flag)) < 0) { - spdlog::error( - "tcp::NonBlockingServer: setsockopt() failed with errno={}", errno); - close_server_socket(); - return std::unexpected{errno}; - } - - return {}; -} - -void NonBlockingServer::close_server_socket() { - if (shutdown(m_serverSocket, 0) < 0) { - spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", - errno); - } - - if (close(m_serverSocket) < 0) { - spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", - errno); - } - - m_serverSocket = -1; - m_serverPfdIn.fd = -1; -} - -void NonBlockingServer::close_client_socket() { - if (shutdown(m_clientSocket, 0) < 0) { - spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", - errno); - } - - if (close(m_clientSocket) < 0) { - spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", - errno); - } - - m_clientSocket = -1; - m_clientPfdIn.fd = -1; -} - - -// -// -// Non blocking client -// -// - - -NonBlockingClient::~NonBlockingClient() { - close_socket(); -} - -[[nodiscard]] std::expected -NonBlockingClient::connect(const HostString& host, uint16_t port) { - /// Resolve host - - auto addrRes = resolve_remote_address(host.data(), port); - if (!addrRes) return std::unexpected{addrRes.error()}; - sockaddr_in serverAddress = addrRes.value(); - - /// Create socket - - close_socket(); - if (auto res = create_socket(); !res) { - return std::unexpected{res.error()}; - } - - /// Connect - - int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress, - sizeof(serverAddress)); - - if (connectRes < 0 && errno != EINPROGRESS) { - spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}", - errno); - return std::unexpected{errno}; - } - - m_startedNewConAttempt = true; - - return {}; -} - -void NonBlockingClient::disconnect() { - m_lastKnownConStatus = ENOTCONN; - close_socket(); -} - -/// -/// @brief Get last known connection status -/// @return - expected(EINPROGRESS) if connection is still being attempted -/// - expected(EISCONN) if already connected -/// - expected(error code) if an error occurred during connection (e.g., -/// ECONNREFUSED) -/// - unexpected(errno) if an error occurred while checking connection -/// status -/// -[[nodiscard]] std::expected -NonBlockingClient::get_last_connection_status() { - if (!m_startedNewConAttempt) return m_lastKnownConStatus; - - /// Check if connect operation has been completed - - auto pendingOpRes = socket_has_new_output_event(); - if (!pendingOpRes) return std::unexpected{pendingOpRes.error()}; - if (!pendingOpRes.value()) return EINPROGRESS; - - m_startedNewConAttempt = false; - - /// Check for connection errors - - int err = 0; - socklen_t len = sizeof(err); - if (getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { - spdlog::error( - "tcp::NonBlockingClient: getsockopt() failed with errno={}", errno); - return std::unexpected{errno}; - } - - if (err == 0) { - m_lastKnownConStatus = EISCONN; - } else { - m_lastKnownConStatus = err; - } - - return m_lastKnownConStatus; -} - -bool NonBlockingClient::data_available() { - auto conRes = get_last_connection_status(); - if (!conRes) return false; - if (conRes.value() != EISCONN) return false; - - int ret = poll(&m_pfdIn, 1, 0); - if (ret < 0) { - spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", - errno); - return false; - } - - return (m_pfdIn.revents & POLLIN); -} - -[[nodiscard]] std::expected -NonBlockingClient::send(std::span data) { - const int bytesWritten = - ::send(m_socket, (const char*)data.data(), data.size(), 0); - - if (bytesWritten <= 0) { - spdlog::error("tcp::NonBlockingClient: send() failed with errno={}", - errno); - return std::unexpected{errno}; - } - - return bytesWritten; -} - -[[nodiscard]] std::expected -NonBlockingClient::recv(std::span buffer) { - const int bytesReceived = - ::recv(m_socket, (char*)buffer.data(), buffer.size(), 0); - - if (bytesReceived < 0) { - spdlog::error("tcp::NonBlockingClient: recv() failed with errno={}", - errno); - return std::unexpected{errno}; - } - return {bytesReceived}; -} - -[[nodiscard]] std::expected NonBlockingClient::create_socket() { - m_socket = socket(AF_INET, SOCK_STREAM, 0); - if (m_socket < 1) { - spdlog::error("tcp::NonBlockingClient: socket() failed with errno={}", - errno); - close_socket(); - return std::unexpected{errno}; - } - - m_pfdOut.fd = m_socket; - m_pfdIn.fd = m_socket; - - auto currentFlags = fcntl(m_socket, F_GETFL, 0); - if (currentFlags == -1) { - spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", - errno); - close_socket(); - return std::unexpected{errno}; - } - - if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { - spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", - errno); - close_socket(); - return std::unexpected{errno}; - } - - return {}; -} - -void NonBlockingClient::close_socket() { - if (shutdown(m_socket, 0) < 0) { - spdlog::debug("tcp::NonBlockingClient: shutdown() failed with errno={}", - errno); - } - - if (close(m_socket) < 0) { - spdlog::debug("tcp::NonBlockingClient: close() failed with errno={}", - errno); - } - - m_socket = -1; - m_pfdOut.fd = -1; - m_pfdIn.fd = -1; -} - - -/// -/// @return true if there is new output event, i.e., the connect() operation has -/// completed (successfully or not) -/// -[[nodiscard]] std::expected -NonBlockingClient::socket_has_new_output_event() { - int ret = poll(&m_pfdOut, 1, 0); - if (ret < 0) { - spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", - errno); - return std::unexpected{errno}; - } - - // ret == 0 means the operation timed out, i.e., no new events - return (ret != 0); -} - - -} // namespace tcp diff --git a/src/tcp_client.cpp b/src/tcp_client.cpp new file mode 100644 index 0000000..7d4e287 --- /dev/null +++ b/src/tcp_client.cpp @@ -0,0 +1,209 @@ +#include "tcp_client.hpp" + +// C stdlib includes +#include +#include +#include + +// Library includes +#include + + +namespace tcp { + + +// +// +// Non blocking client +// +// + + +NonBlockingClient::~NonBlockingClient() { + close_socket(); +} + +[[nodiscard]] std::expected +NonBlockingClient::connect(const HostString& host, uint16_t port) { + /// Resolve host + + auto addrRes = resolve_remote_address(host.data(), port); + if (!addrRes) return std::unexpected{addrRes.error()}; + sockaddr_in serverAddress = addrRes.value(); + + /// Create socket + + close_socket(); + if (auto res = create_socket(); !res) { + return std::unexpected{res.error()}; + } + + /// Connect + + int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress, + sizeof(serverAddress)); + + if (connectRes < 0 && errno != EINPROGRESS) { + spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}", + errno); + return std::unexpected{errno}; + } + + m_startedNewConAttempt = true; + + return {}; +} + +void NonBlockingClient::disconnect() { + m_lastKnownConStatus = ENOTCONN; + close_socket(); +} + +/// +/// @brief Get last known connection status +/// @return - expected(EINPROGRESS) if connection is still being attempted +/// - expected(EISCONN) if already connected +/// - expected(error code) if an error occurred during connection (e.g., +/// ECONNREFUSED) +/// - unexpected(errno) if an error occurred while checking connection +/// status +/// +[[nodiscard]] std::expected +NonBlockingClient::get_last_connection_status() { + if (!m_startedNewConAttempt) return m_lastKnownConStatus; + + /// Check if connect operation has been completed + + auto pendingOpRes = socket_has_new_output_event(); + if (!pendingOpRes) return std::unexpected{pendingOpRes.error()}; + if (!pendingOpRes.value()) return EINPROGRESS; + + m_startedNewConAttempt = false; + + /// Check for connection errors + + int err = 0; + socklen_t len = sizeof(err); + if (getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + spdlog::error( + "tcp::NonBlockingClient: getsockopt() failed with errno={}", errno); + return std::unexpected{errno}; + } + + if (err == 0) { + m_lastKnownConStatus = EISCONN; + } else { + m_lastKnownConStatus = err; + } + + return m_lastKnownConStatus; +} + +bool NonBlockingClient::data_available() { + auto conRes = get_last_connection_status(); + if (!conRes) return false; + if (conRes.value() != EISCONN) return false; + + int ret = poll(&m_pfdIn, 1, 0); + if (ret < 0) { + spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", + errno); + return false; + } + + return (m_pfdIn.revents & POLLIN); +} + +[[nodiscard]] std::expected +NonBlockingClient::send(std::span data) { + const int bytesWritten = + ::send(m_socket, (const char*)data.data(), data.size(), 0); + + if (bytesWritten <= 0) { + spdlog::error("tcp::NonBlockingClient: send() failed with errno={}", + errno); + return std::unexpected{errno}; + } + + return bytesWritten; +} + +[[nodiscard]] std::expected +NonBlockingClient::recv(std::span buffer) { + const int bytesReceived = + ::recv(m_socket, (char*)buffer.data(), buffer.size(), 0); + + if (bytesReceived < 0) { + spdlog::error("tcp::NonBlockingClient: recv() failed with errno={}", + errno); + return std::unexpected{errno}; + } + return {bytesReceived}; +} + +[[nodiscard]] std::expected NonBlockingClient::create_socket() { + m_socket = socket(AF_INET, SOCK_STREAM, 0); + if (m_socket < 1) { + spdlog::error("tcp::NonBlockingClient: socket() failed with errno={}", + errno); + close_socket(); + return std::unexpected{errno}; + } + + m_pfdOut.fd = m_socket; + m_pfdIn.fd = m_socket; + + auto currentFlags = fcntl(m_socket, F_GETFL, 0); + if (currentFlags == -1) { + spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", + errno); + close_socket(); + return std::unexpected{errno}; + } + + if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { + spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}", + errno); + close_socket(); + return std::unexpected{errno}; + } + + return {}; +} + +void NonBlockingClient::close_socket() { + if (shutdown(m_socket, 0) < 0) { + spdlog::debug("tcp::NonBlockingClient: shutdown() failed with errno={}", + errno); + } + + if (close(m_socket) < 0) { + spdlog::debug("tcp::NonBlockingClient: close() failed with errno={}", + errno); + } + + m_socket = -1; + m_pfdOut.fd = -1; + m_pfdIn.fd = -1; +} + + +/// +/// @return true if there is new output event, i.e., the connect() operation has +/// completed (successfully or not) +/// +[[nodiscard]] std::expected +NonBlockingClient::socket_has_new_output_event() { + int ret = poll(&m_pfdOut, 1, 0); + if (ret < 0) { + spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}", + errno); + return std::unexpected{errno}; + } + + // ret == 0 means the operation timed out, i.e., no new events + return (ret != 0); +} + + +} // namespace tcp diff --git a/src/tcp_client.hpp b/src/tcp_client.hpp new file mode 100644 index 0000000..37734ca --- /dev/null +++ b/src/tcp_client.hpp @@ -0,0 +1,64 @@ +#pragma once + + +// STL includes +#include +#include +#include +#include + +// C stdlib includes +#include +#include + +// Project includes +#include "common.hpp" + + +namespace tcp { + + +// +// +// Non blocking client +// +// + + +class NonBlockingClient { +public: + NonBlockingClient() = default; + NonBlockingClient(const NonBlockingClient&) = delete; + NonBlockingClient(NonBlockingClient&&) = delete; + NonBlockingClient& operator=(const NonBlockingClient&) = delete; + NonBlockingClient& operator=(NonBlockingClient&&) = delete; + ~NonBlockingClient(); + + [[nodiscard]] std::expected connect(const HostString& host, + uint16_t port); + + void disconnect(); + + [[nodiscard]] std::expected get_last_connection_status(); + + bool data_available(); + + [[nodiscard]] std::expected send(std::span data); + [[nodiscard]] std::expected recv(std::span buffer); + +private: + int m_socket = -1; + pollfd m_pfdOut = {.fd = -1, .events = POLLOUT}; + pollfd m_pfdIn = {.fd = -1, .events = POLLIN}; + + bool m_startedNewConAttempt = false; + int m_lastKnownConStatus = ENOTCONN; + + [[nodiscard]] std::expected create_socket(); + void close_socket(); + + [[nodiscard]] std::expected socket_has_new_output_event(); +}; + + +} // namespace tcp diff --git a/src/tcp_server.cpp b/src/tcp_server.cpp new file mode 100644 index 0000000..663651a --- /dev/null +++ b/src/tcp_server.cpp @@ -0,0 +1,222 @@ +#include "tcp_server.hpp" + +// C stdlib includes +#include +#include +#include + +// Library includes +#include + + +namespace tcp { + + +// +// +// Non blocking server +// +// + + +NonBlockingServer::~NonBlockingServer() { + close_client_socket(); + close_server_socket(); +} + +/// +/// @details Calling this repeatedly closes the previous socket and creates a +/// new one. +/// +std::expected NonBlockingServer::start_listening(uint16_t port) { + /// Create socket + + close_server_socket(); + if (auto res = create_server_socket(); !res) { + return std::unexpected{res.error()}; + } + + /// Bin socket and start listening + + const struct sockaddr_in serverAddress = get_local_address(port); + + if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress, + sizeof(serverAddress)) != 0) { + spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}", + errno); + close_server_socket(); + return std::unexpected{errno}; + }; + + if (::listen(m_serverSocket, 1) != 0) { + spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}", + errno); + close_server_socket(); + return std::unexpected{errno}; + } + + return {}; +} + +void NonBlockingServer::stop_listening() { + close_server_socket(); +} + +bool NonBlockingServer::next_client_available() { + int ret = poll(&m_serverPfdIn, 1, 0); + if (ret < 0) { + spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", + errno); + return false; + } + + return (m_serverPfdIn.revents & POLLIN); +} + +[[nodiscard]] std::expected NonBlockingServer::accept_next_client() { + disconnect_from_client(); + + socklen_t clientAddrLen = sizeof(m_clientAddress); + + int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress, + &clientAddrLen); + if (result < 0) return std::unexpected{errno}; + + m_clientSocket = result; + m_clientPfdIn.fd = m_clientSocket; + + return {}; +} + +void NonBlockingServer::disconnect_from_client() { + close_client_socket(); +} + +[[nodiscard]] std::expected, int> +NonBlockingServer::get_client_ip_as_string() { + std::array addrStr; + + if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr, + addrStr.data(), addrStr.size()) == nullptr) { + spdlog::error( + "tcp::NonBlockingServer: inet_ntop() failed with errno={}", errno); + return std::unexpected{errno}; + } + + // if (inet_ntoa_r(((struct sockaddr_in*)&m_clientAddress)->sin_addr, + // addrStr.data(), addrStr.size()) == nullptr) { + // return std::unexpected{errno}; + // } + + return addrStr; +} + +bool NonBlockingServer::data_available() { + int ret = poll(&m_clientPfdIn, 1, 0); + if (ret < 0) { + spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}", + errno); + return false; + } + + return (m_clientPfdIn.revents & POLLIN); +} + +[[nodiscard]] std::expected +NonBlockingServer::send(std::span data) { + const int bytesWritten = + ::send(m_clientSocket, (const char*)data.data(), data.size(), 0); + + if (bytesWritten <= 0) { + spdlog::error("tcp::NonBlockingServer: send() failed with errno={}", + errno); + return std::unexpected{errno}; + } + + return bytesWritten; +} + +[[nodiscard]] std::expected +NonBlockingServer::recv(std::span buffer) { + const int bytesReceived = + ::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0); + + if (bytesReceived < 0) { + spdlog::error("tcp::NonBlockingServer: recv() failed with errno={}", + errno); + return std::unexpected{errno}; + } + return {bytesReceived}; +} + +[[nodiscard]] std::expected +NonBlockingServer::create_server_socket() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, 0); + if (m_serverSocket < 1) { + spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}", + errno); + close_server_socket(); + return std::unexpected{errno}; + } + + m_serverPfdIn.fd = m_serverSocket; + + auto currentFlags = fcntl(m_serverSocket, F_GETFL, 0); + if (currentFlags == -1) { + spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", + errno); + close_server_socket(); + return std::unexpected{errno}; + } + + if (fcntl(m_serverSocket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) { + spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}", + errno); + close_server_socket(); + return std::unexpected{errno}; + } + + int flag = 1; + if (::setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &flag, + sizeof(flag)) < 0) { + spdlog::error( + "tcp::NonBlockingServer: setsockopt() failed with errno={}", errno); + close_server_socket(); + return std::unexpected{errno}; + } + + return {}; +} + +void NonBlockingServer::close_server_socket() { + if (shutdown(m_serverSocket, 0) < 0) { + spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", + errno); + } + + if (close(m_serverSocket) < 0) { + spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", + errno); + } + + m_serverSocket = -1; + m_serverPfdIn.fd = -1; +} + +void NonBlockingServer::close_client_socket() { + if (shutdown(m_clientSocket, 0) < 0) { + spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}", + errno); + } + + if (close(m_clientSocket) < 0) { + spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}", + errno); + } + + m_clientSocket = -1; + m_clientPfdIn.fd = -1; +} + + +} // namespace tcp diff --git a/src/tcp.hpp b/src/tcp_server.hpp similarity index 55% rename from src/tcp.hpp rename to src/tcp_server.hpp index 77ee8eb..175e24b 100644 --- a/src/tcp.hpp +++ b/src/tcp_server.hpp @@ -2,7 +2,6 @@ // STL includes -#include #include #include #include @@ -12,13 +11,13 @@ #include #include +// Project includes +#include "common.hpp" + namespace tcp { -using HostString = std::array; - - // // // Non blocking server @@ -65,47 +64,4 @@ private: }; -// -// -// Non blocking client -// -// - - -class NonBlockingClient { -public: - NonBlockingClient() = default; - NonBlockingClient(const NonBlockingClient&) = delete; - NonBlockingClient(NonBlockingClient&&) = delete; - NonBlockingClient& operator=(const NonBlockingClient&) = delete; - NonBlockingClient& operator=(NonBlockingClient&&) = delete; - ~NonBlockingClient(); - - [[nodiscard]] std::expected connect(const HostString& host, - uint16_t port); - - void disconnect(); - - [[nodiscard]] std::expected get_last_connection_status(); - - bool data_available(); - - [[nodiscard]] std::expected send(std::span data); - [[nodiscard]] std::expected recv(std::span buffer); - -private: - int m_socket = -1; - pollfd m_pfdOut = {.fd = -1, .events = POLLOUT}; - pollfd m_pfdIn = {.fd = -1, .events = POLLIN}; - - bool m_startedNewConAttempt = false; - int m_lastKnownConStatus = ENOTCONN; - - [[nodiscard]] std::expected create_socket(); - void close_socket(); - - [[nodiscard]] std::expected socket_has_new_output_event(); -}; - - } // namespace tcp diff --git a/tests/tcp.cpp b/tests/tcp.cpp index 374aee3..7e39bd5 100644 --- a/tests/tcp.cpp +++ b/tests/tcp.cpp @@ -4,7 +4,8 @@ #include // Project includes -#include "tcp.hpp" +#include "tcp_server.hpp" +#include "tcp_client.hpp" TEST(TcpServer, Accept) {