From 9338f3b270111eb88dacd559b6255815fffe413a Mon Sep 17 00:00:00 2001 From: roby2014 Date: Mon, 15 Jul 2024 18:58:37 +0100 Subject: [PATCH] feat(core): impl network TcpListener class --- core/include/cubos/core/net/tcp_listener.hpp | 72 ++++++++++ core/src/net/tcp_listener.cpp | 134 +++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 core/include/cubos/core/net/tcp_listener.hpp create mode 100644 core/src/net/tcp_listener.cpp diff --git a/core/include/cubos/core/net/tcp_listener.hpp b/core/include/cubos/core/net/tcp_listener.hpp new file mode 100644 index 000000000..64be937db --- /dev/null +++ b/core/include/cubos/core/net/tcp_listener.hpp @@ -0,0 +1,72 @@ +/// @file +/// @brief Class @ref cubos::core::net::TcpListener. +/// @ingroup core-net + +#pragma once + +#include +#include +#include +#include + +namespace cubos::core::net +{ + /// @brief A TCP socket server, listening for connections. + /// @ingroup core-net + class CUBOS_CORE_API TcpListener final + { + public: + /// @brief Create an empty TCP listener, ready for connecting. + TcpListener(); + + /// @brief Deconstructs by calling @ref close. + ~TcpListener(); + + /// @brief Forbid copy construction. + TcpListener(const TcpListener&) = delete; + + /// @brief Forbid assignment. + TcpListener& operator=(const TcpListener&) = delete; + + /// @brief Start listening for connection at specified `address` and `port`. + /// @param address Destination address. + /// @param port Destination port. + /// @param maxQueueLength Maximum connection requests. + /// @return Whether start listening was successful. + bool listen(const Address& address, uint16_t port, int maxQueueLength = 4096); + + /// @brief Accepts a new connection. + /// @param[out] stream Buffer to store the connection stream. + /// @return Whether accepting the connection and creating the stream was successful. + /// @note This function will block until a connection is made. + bool accept(TcpStream& stream) const; + + /// @brief Close the listener socket. + void close(); + + /// @brief Check if connection is valid. + /// @return Whether inner sock is connected. + inline bool valid() const + { + return mSock != InnerInvalidSocket; + } + + /// @brief Get inner socket handle. + /// @return Socket. + inline InnerSocket inner() const + { + return mSock; + } + + /// @brief Control blocking mode of inner socket. + /// @note A blocking socket does not return control until it has sent (or received) some or all data specified + /// for the operation. + /// @param blocking Whether to block or not. + /// @return Whether setting the block flag was sucessful. + bool setBlocking(bool blocking); + + private: + InnerSocket mSock{InnerInvalidSocket}; + bool mBlocking{true}; + }; +} // namespace cubos::core::net \ No newline at end of file diff --git a/core/src/net/tcp_listener.cpp b/core/src/net/tcp_listener.cpp new file mode 100644 index 000000000..246962519 --- /dev/null +++ b/core/src/net/tcp_listener.cpp @@ -0,0 +1,134 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +using cubos::core::net::Address; +using cubos::core::net::TcpListener; +using cubos::core::net::TcpStream; + +TcpListener::TcpListener() +{ +#ifdef _WIN32 + WSADATA wsa; + CUBOS_ASSERT(WSAStartup(MAKEWORD(2, 2), &wsa) == 0, " WSAStartup failed: {}", WSAGetLastError()); +#endif + setBlocking(mBlocking); +} + +TcpListener::~TcpListener() +{ + close(); +} + +bool TcpListener::listen(const Address& address, uint16_t port, int maxQueueLength) +{ + close(); + + mSock = socket(AF_INET, SOCK_STREAM, 0); + if (mSock == InnerInvalidSocket) + { + CUBOS_ERROR("Failed to allocate TCP socket: {}", logSystemError()); + return false; + } + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(address.toString().c_str()); + + if (::bind(mSock, reinterpret_cast(&addr), sizeof(addr)) == -1) + { + CUBOS_ERROR("Failed to bind TCP socket: {}", logSystemError()); + return false; + } + + if (::listen(mSock, maxQueueLength) < 0) + { + CUBOS_ERROR("Failed to listen to TCP socket: {}", logSystemError()); + return false; + } + + CUBOS_DEBUG("TCP listener socket '{}' bound to address {} with port '{}'", mSock, address.toString(), port); + return true; +} + +bool TcpListener::accept(TcpStream& stream) const +{ + if (mSock == InnerInvalidSocket) + { + CUBOS_ERROR("Listener hasn't been initialized correctly"); + return false; + } + + auto clientSocket = ::accept(mSock, nullptr, nullptr); + if (clientSocket == InnerInvalidSocket) + { + CUBOS_ERROR("Failed to accept TCP connection: {}", logSystemError()); + return false; + } + + stream.inner(clientSocket); + return true; +} + +void TcpListener::close() +{ + if (mSock != InnerInvalidSocket) + { +#ifdef _WIN32 + closesocket(mSock); + WSACleanup(); +#else + ::close(mSock); +#endif + mSock = InnerInvalidSocket; + } +} + +bool TcpListener::setBlocking(bool blocking) +{ + bool success = false; + +#ifdef _WIN32 + u_long flags = blocking ? 0 : 1; + success = NO_ERROR == ioctlsocket(mSock, FIONBIO, &flags); +#else + mBlocking = blocking; + + int flags = fcntl(mSock, F_GETFL, 0); + + if (blocking) + { + flags &= ~O_NONBLOCK; + } + else + { + flags |= O_NONBLOCK; + } + + success = fcntl(mSock, F_SETFL, flags) == 0; +#endif + if (!success) + { + CUBOS_ERROR("Failed to change block flag of socket '{}'", mSock); + } + + return success; +} \ No newline at end of file