From 5121c2a27bf107e6fbed7039c103ea126e65b2b4 Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Sat, 19 Apr 2025 02:35:03 +0200 Subject: [PATCH] Add tcp server --- CMakeLists.txt | 4 +- scripts/tcp_client.py | 45 +++++++- src/main.cpp | 172 +++++++++++++++-------------- src/tcp.cpp | 249 +++++++++++++++++++++++++++++++++++++----- src/tcp.hpp | 78 +++++++++---- 5 files changed, 409 insertions(+), 139 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c186bf..0748800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,8 @@ FetchContent_MakeAvailable(googletest) # -# add_executable(playground src/main.cpp src/tcp.cpp) -# target_link_libraries(playground PRIVATE spdlog::spdlog) +add_executable(playground src/main.cpp src/tcp.cpp) +target_link_libraries(playground PRIVATE spdlog::spdlog) # diff --git a/scripts/tcp_client.py b/scripts/tcp_client.py index 89e7fcd..3639e15 100644 --- a/scripts/tcp_client.py +++ b/scripts/tcp_client.py @@ -1,15 +1,48 @@ import socket +import sys +import logging +from time import sleep def main(): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + datefmt='%H:%M:%S', + level=logging.DEBUG, + stream=sys.stdout) - sock.connect(("localhost", 1234)) - sock.sendall(b"asdf") - data = sock.recv(1024) - print(data.decode()) + host = "localhost" + port = 65432 + + while True: + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.connect((host, port)) + + client_socket.send(b"""{ + "jsonrpc": "2.0", + "id": "1", + "method": "SetLedPattern", + "params": { + "led": 19, + "pattern": 3.1 + } + } + """) + + try: + res = client_socket.recv(1024) + if not res: + logging.info("Client disconnected") + break + + logging.info(f"SetLedPattern response: {res.decode()}") + except socket.timeout: + logging.error("Connection timed out") + break + + client_socket.close() + + sleep(1) if __name__ == "__main__": main() - diff --git a/src/main.cpp b/src/main.cpp index 335cf38..26a61eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,101 +1,103 @@ #include "tcp.hpp" +#include #include - int main() { spdlog::set_level(spdlog::level::debug); - tcp::NonBlockingClient client; - - tcp::HostString host = {"192.168.0.12"}; - uint16_t port = 123; - - spdlog::info("Connecting to {}:{}", host.data(), port); - - if (!client.connect(host, port)) std::terminate(); - - tcp::ConnectionStatus status = tcp::ConnectionStatus::AttemptingConnection; - while (status == tcp::ConnectionStatus::AttemptingConnection) { - auto statusRes = client.get_last_connection_status(); - if (!statusRes) std::terminate(); - status = statusRes.value(); + tcp::NonBlockingServer server{{.port = 65432}}; + if (auto res = server.start_listening(); !res) { + spdlog::error("Failed to start server: {}", res.error()); + return 1; } - switch (status) { - case tcp::ConnectionStatus::Successful: - spdlog::info("Connected successfully"); - break; - case tcp::ConnectionStatus::Refused: - spdlog::error("Connection refused"); - std::terminate(); - case tcp::ConnectionStatus::NoRouteToHost: - spdlog::error("No route to host"); - std::terminate(); - case tcp::ConnectionStatus::TimedOut: - spdlog::error("Connection timed out"); - std::terminate(); - case tcp::ConnectionStatus::HostUnreachable: - spdlog::error("Host unreachable"); - std::terminate(); - case tcp::ConnectionStatus::OtherError: - spdlog::error("Other error"); - std::terminate(); - case tcp::ConnectionStatus::AttemptingConnection: - spdlog::error("Attempting connection"); - std::terminate(); - } + spdlog::info("Server started, waiting for connections..."); - char buffer[1024]; + while (true) { + while (!server.next_client_available()) + ; - while (!client.data_available()) - ; - - auto recvRes = client.recv(std::span(buffer, sizeof(buffer))); - - if (!recvRes) { - switch (recvRes.error()) { - case tcp::Error::FailedToCreateSocket: - spdlog::error("Failed to create socket"); - case tcp::Error::FailedToStartConnecting: - spdlog::error("Failed to start connecting"); - case tcp::Error::FailedToReadSocketError: - spdlog::error("Failed to read socket error"); - case tcp::Error::FailedToResolveHost: - spdlog::error("Failed to resolve host"); - case tcp::Error::OtherOperationInProgress: - spdlog::error("Other operation in progress"); - case tcp::Error::SocketNotConnected: - spdlog::error("Socket not connected"); - case tcp::Error::BufferFull: - spdlog::error("Buffer full"); - case tcp::Error::NoDataAvailable: - spdlog::error("No data available"); - case tcp::Error::SendFailed: - spdlog::error("Send failed"); - case tcp::Error::RecvFailed: - spdlog::error("Recv failed"); - break; + if (auto res = server.accept_next_client(); !res) { + spdlog::error("Failed to accept client: {}", res.error()); + continue; } - std::terminate(); - } + spdlog::info("Client connected"); - spdlog::info("Received {} bytes", recvRes.value()); - if (recvRes.value() > 0) { + std::array buffer; + + while (!server.data_available()) + ; + + auto recvRes = server.recv(std::span(buffer)); + if (!recvRes) { + spdlog::error("Failed to receive data: {}", recvRes.error()); + break; + } spdlog::info("Received message: {}", - std::string(buffer, recvRes.value())); + std::string(reinterpret_cast(buffer.data()), + recvRes.value())); + + const char* msg = "Hello, world!"; + auto sendRes = server.send(std::as_bytes(std::span(msg, strlen(msg)))); + + if (!sendRes) { + spdlog::error("Failed to send message: {}", sendRes.error()); + } else { + spdlog::info("Sent {} bytes", sendRes.value()); + } + + server.disconnect(); } - - EAGAIN; - - const char* msg = "Hello, world!"; - auto sendRes = client.send(std::span(msg, strlen(msg))); - if (!sendRes) { - spdlog::error("Failed to send message"); - std::terminate(); - } - spdlog::info("Sent {} bytes", sendRes.value()); - client.disconnect(); - - return 0; } + +// int main() { +// spdlog::set_level(spdlog::level::debug); +// +// tcp::NonBlockingClient client; +// +// tcp::HostString host = {"localhost"}; +// uint16_t port = 65432; +// +// spdlog::info("Connecting to {}:{}", host.data(), port); +// +// if (!client.connect(host, port)) std::terminate(); +// +// +// int status = EINPROGRESS; +// while (status == EINPROGRESS) { +// auto statusRes = client.get_last_connection_status(); +// if (!statusRes) std::terminate(); +// status = statusRes.value(); +// } +// +// char buffer[1024]; +// +// while (!client.data_available()) +// ; +// +// auto recvRes = client.recv(std::span(buffer, sizeof(buffer))); +// +// if (!recvRes) { +// std::terminate(); +// } +// +// spdlog::info("Received {} bytes", recvRes.value()); +// if (recvRes.value() > 0) { +// spdlog::info("Received message: {}", +// std::string(buffer, recvRes.value())); +// } +// +// EINPROGRESS; +// +// const char* msg = "Hello, world!"; +// auto sendRes = client.send(std::span(msg, +// strlen(msg))); if (!sendRes) { +// spdlog::error("Failed to send message"); +// std::terminate(); +// } +// spdlog::info("Sent {} bytes", sendRes.value()); +// client.disconnect(); +// +// return 0; +// } diff --git a/src/tcp.cpp b/src/tcp.cpp index b846dff..44d5932 100644 --- a/src/tcp.cpp +++ b/src/tcp.cpp @@ -18,8 +18,8 @@ namespace { -std::expected get_server_address(const char* host, - uint16_t port) { +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={}", @@ -36,6 +36,15 @@ std::expected get_server_address(const char* host, return address; } +inline auto get_local_address(uint16_t port) -> sockaddr_in { + sockaddr_in address{}; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_family = AF_INET; + address.sin_port = htons(port); + return address; +} + + } // namespace @@ -43,11 +52,213 @@ std::expected get_server_address(const char* host, namespace tcp { +// +// +// Non blocking server +// +// + + +NonBlockingServer::NonBlockingServer(ServerSettings settings) + : m_settings{settings}, m_serverAddress(get_local_address(settings.port)) { +} + +std::expected NonBlockingServer::start_listening() { + /// Create socket + + close_server_socket(); + if (auto res = create_socket(); !res) { + return std::unexpected{res.error()}; + } + + /// Bin socket and start listening + + if (::bind(m_serverSocket, (struct sockaddr*)&m_serverAddress, + sizeof(m_serverAddress)) != 0) { + spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}", + errno); + close(m_serverSocket); + return std::unexpected{errno}; + }; + + if (::listen(m_serverSocket, 1) != 0) { + spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}", + errno); + close(m_serverSocket); + return std::unexpected{errno}; + } + + return {}; +} + +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(); + + int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress, + &m_clientAddrLen); + if (result < 0) return std::unexpected{errno}; + + m_clientSocket = result; + m_clientPfdIn.fd = m_clientSocket; + + return {}; +} +void NonBlockingServer::disconnect() { + close_client_socket(); +} + +// TODO: Do we need this? +// bool NonBlockingServer::socket_closed() const { +// } + +[[nodiscard]] std::expected, int> +NonBlockingServer::get_client_ip_as_string() const { + 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}; +} + +[[nodiscard]] std::expected +NonBlockingServer::send(std::span data) const { + 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 { + 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}; +} + +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::create_socket() { + m_serverSocket = socket(AF_INET, SOCK_STREAM, 0); + if (m_serverSocket < 1) { + spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}", + errno); + 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 +// +// + + [[nodiscard]] std::expected NonBlockingClient::connect(HostString host, uint16_t port) { /// Resolve host - auto addrRes = get_server_address(host.data(), port); + auto addrRes = resolve_remote_address(host.data(), port); if (!addrRes) return std::unexpected{addrRes.error()}; sockaddr_in serverAddress = addrRes.value(); @@ -63,7 +274,7 @@ NonBlockingClient::connect(HostString host, uint16_t port) { int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)); - if (connectRes < 0) { + if (connectRes < 0 && errno != EINPROGRESS) { spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}", errno); return std::unexpected{errno}; @@ -74,7 +285,7 @@ NonBlockingClient::connect(HostString host, uint16_t port) { return {}; } -[[nodiscard]] std::expected +[[nodiscard]] std::expected NonBlockingClient::get_last_connection_status() { if (!m_startedNewConAttemt) return m_lastKnownConStatus; @@ -82,7 +293,7 @@ NonBlockingClient::get_last_connection_status() { auto pendingOpRes = new_error_codes_available(); if (!pendingOpRes) return std::unexpected{pendingOpRes.error()}; - if (pendingOpRes.value()) return ConnectionStatus::AttemptingConnection; + if (!pendingOpRes.value()) return EINPROGRESS; m_startedNewConAttemt = false; @@ -97,25 +308,9 @@ NonBlockingClient::get_last_connection_status() { } if (err == 0) { - m_lastKnownConStatus = ConnectionStatus::Successful; + m_lastKnownConStatus = EISCONN; } else { - switch (err) { - case ECONNREFUSED: - m_lastKnownConStatus = ConnectionStatus::Refused; - break; - case ENETUNREACH: - m_lastKnownConStatus = ConnectionStatus::NoRouteToHost; - break; - case ETIMEDOUT: - m_lastKnownConStatus = ConnectionStatus::TimedOut; - break; - case EHOSTUNREACH: - m_lastKnownConStatus = ConnectionStatus::HostUnreachable; - break; - default: - m_lastKnownConStatus = ConnectionStatus::OtherError; - break; - } + m_lastKnownConStatus = err; } return m_lastKnownConStatus; @@ -130,7 +325,7 @@ NonBlockingClient::send(std::span data) { // TODO: Do we need this? auto conRes = get_last_connection_status(); if (!conRes) return std::unexpected{conRes.error()}; - if (conRes != ConnectionStatus::Successful) { + if (conRes != EISCONN) { spdlog::error("tcp::NonBlockingClient: Socket not connected"); return std::unexpected{ENOTCONN}; } @@ -150,7 +345,7 @@ NonBlockingClient::send(std::span data) { bool NonBlockingClient::data_available() { auto conRes = get_last_connection_status(); if (!conRes) return false; - if (conRes != ConnectionStatus::Successful) return false; + if (conRes != EISCONN) return false; int ret = poll(&m_pfdIn, 1, 0); if (ret < 0) { @@ -230,7 +425,7 @@ NonBlockingClient::new_error_codes_available() { return std::unexpected{errno}; } - return (ret == 0); + return (ret != 0); } diff --git a/src/tcp.hpp b/src/tcp.hpp index ec31000..b01c587 100644 --- a/src/tcp.hpp +++ b/src/tcp.hpp @@ -24,27 +24,65 @@ namespace tcp { -enum class ConnectionStatus { - AttemptingConnection, - Successful, - Refused, - NoRouteToHost, - TimedOut, - HostUnreachable, - OtherError, +using HostString = std::array; + +// +// +// Non blocking server +// +// + +struct ServerSettings { + uint16_t port = 0; +}; + +class NonBlockingServer { +public: + NonBlockingServer(ServerSettings settings); + + [[nodiscard]] std::expected start_listening(); + + bool next_client_available(); + [[nodiscard]] std::expected accept_next_client(); + void disconnect(); + // bool socket_closed() const; + + [[nodiscard]] std::expected + send(std::span data) const; + + [[nodiscard]] std::expected + recv(std::span buffer) const; + + bool data_available(); + + [[nodiscard]] std::expected, int> + get_client_ip_as_string() const; + +private: + ServerSettings m_settings; + const struct sockaddr_in m_serverAddress; + int m_serverSocket = -1; + + pollfd m_serverPfdIn = {.fd = -1, .events = POLLIN}; + pollfd m_clientPfdIn = {.fd = -1, .events = POLLIN}; + + int m_clientSocket = -1; + struct sockaddr m_clientAddress; + socklen_t m_clientAddrLen = sizeof(m_clientAddress); + + + [[nodiscard]] std::expected create_socket(); + void close_server_socket(); + void close_client_socket(); }; -using HostString = std::array; - -// struct ServerSettings { -// uint16_t port = 0; -// }; // -// class NonBlockingServer { -// public: -// NonBlockingServer(ServerSettings settings); -// }; +// +// Non blocking client +// +// + class NonBlockingClient { public: @@ -55,11 +93,12 @@ public: void disconnect(); - [[nodiscard]] std::expected + [[nodiscard]] std::expected get_last_connection_status(); bool data_available(); + // TODO: Make this std::byte [[nodiscard]] std::expected recv(std::span buffer); [[nodiscard]] std::expected send(std::span data); @@ -69,7 +108,7 @@ private: pollfd m_pfdIn = {.fd = -1, .events = POLLIN}; bool m_startedNewConAttemt = false; - ConnectionStatus m_lastKnownConStatus = ConnectionStatus::OtherError; + int m_lastKnownConStatus = 0; void close_socket(); @@ -77,4 +116,5 @@ private: [[nodiscard]] std::expected new_error_codes_available(); }; + } // namespace tcp