From 7d71515d2243a9311a18363d650a1a6605e3c614 Mon Sep 17 00:00:00 2001 From: "Ivan I. Ovchinnikov" Date: Thu, 28 May 2026 22:26:46 +0300 Subject: [PATCH] vibes --- CMakeLists.txt | 38 +++++- .../network/server_socket/ServerSocket.h | 52 +++++++- .../server_socket/ServerSocketListener.h | 8 +- .../server_socket/ServerSocketWrapper.h | 52 ++++++++ src/ServerSocket.cpp | 125 ++++++++++++++++++ src/ServerSocketWrapper.cpp | 97 ++++++++++++++ .../network/server_socket/ServerSocket.cpp | 7 - .../server_socket/ServerSocketWrapper.cpp | 3 - 8 files changed, 360 insertions(+), 22 deletions(-) rename {src => include}/iovi_space/network/server_socket/ServerSocketListener.h (82%) create mode 100644 src/ServerSocket.cpp create mode 100644 src/ServerSocketWrapper.cpp delete mode 100644 src/iovi_space/network/server_socket/ServerSocket.cpp delete mode 100644 src/iovi_space/network/server_socket/ServerSocketWrapper.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eb374db..2559e44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,35 @@ -cmake_minimum_required(VERSION 4.1) -project(Network) +cmake_minimum_required(VERSION 3.28) +project(iovi_space_network LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_library(Network STATIC - src/iovi_space/network/server_socket/ServerSocket.cpp - include/iovi_space/network/server_socket/ServerSocket.h +# Исходники и заголовки +set(HEADERS + include/iovi_space/network/server_socket/ServerSocketListener.h include/iovi_space/network/server_socket/ServerSocketWrapper.h - src/iovi_space/network/server_socket/ServerSocketWrapper.cpp - src/iovi_space/network/server_socket/ServerSocketListener.h) + include/iovi_space/network/server_socket/ServerSocket.h +) + +set(SOURCES + src/ServerSocketWrapper.cpp + src/ServerSocket.cpp +) + +# Создаём статическую библиотеку +add_library(iovi_space_network STATIC ${SOURCES} ${HEADERS}) + +# Публичные заголовки для подключения извне +target_include_directories(iovi_space_network + PUBLIC + $ + $ +) + +# Линковка системных библиотек для сокетов +if(WIN32) + target_link_libraries(iovi_space_network PRIVATE ws2_32) + target_compile_definitions(iovi_space_network PRIVATE _WINSOCK_DEPRECATED_NO_WARNINGS) +elseif(UNIX AND NOT APPLE) + target_link_libraries(iovi_space_network PRIVATE pthread) +endif() diff --git a/include/iovi_space/network/server_socket/ServerSocket.h b/include/iovi_space/network/server_socket/ServerSocket.h index 6cd641e..b8de910 100644 --- a/include/iovi_space/network/server_socket/ServerSocket.h +++ b/include/iovi_space/network/server_socket/ServerSocket.h @@ -1,6 +1,56 @@ #ifndef NETWORK_LIBRARY_H #define NETWORK_LIBRARY_H -void hello(); +#pragma once +#include "ServerSocketListener.h" +#include "ServerSocketWrapper.h" +#include +#include +#include +#include + +namespace iovi_space::network { + + /** + * @brief Поток серверного сокета. + * Аналог Java ServerSocketThread. + * + * Запускает внутренний std::thread, который ожидает подключения + * и делегирует события через ServerSocketListener. + */ + class ServerSocket { + public: + /** + * @param listener Указатель на реализацию интерфейса (не владеет) + * @param name Имя потока (для отладки) + * @param port Порт для прослушивания + * @param timeout_ms Таймаут accept() в миллисекундах + */ + ServerSocket(ServerSocketListener* listener, const std::string& name, int port, int timeout_ms); + + ~ServerSocket(); + + // Остановка сервера (аналог interrupt() в Java) + void stop(); + + [[nodiscard]] bool isRunning() const { return running_.load(); } + [[nodiscard]] int getPort() const { return port_; } + + private: + void run(); // Точка входа потока + + ServerSocketListener* listener_; // Не владеет + std::string name_; + int port_; + int timeout_ms_; + + std::thread worker_; + std::atomic running_{false}; + std::atomic stop_requested_{false}; + + ServerSocketWrapper listenSocket_; + }; + +} // namespace iovi_space::network #endif // NETWORK_LIBRARY_H \ No newline at end of file diff --git a/src/iovi_space/network/server_socket/ServerSocketListener.h b/include/iovi_space/network/server_socket/ServerSocketListener.h similarity index 82% rename from src/iovi_space/network/server_socket/ServerSocketListener.h rename to include/iovi_space/network/server_socket/ServerSocketListener.h index 6919b3e..9cb3d0d 100644 --- a/src/iovi_space/network/server_socket/ServerSocketListener.h +++ b/include/iovi_space/network/server_socket/ServerSocketListener.h @@ -12,7 +12,7 @@ namespace iovi_space::network { // Forward declaration class ServerSocket; - class SocketWrapper; + class ServerSocketWrapper; /** * @brief Абстрактный интерфейс слушателя событий сервера. @@ -24,9 +24,9 @@ namespace iovi_space::network { virtual void onServerStart(ServerSocket* server) = 0; virtual void onServerStop(ServerSocket* server) = 0; - virtual void onServerSocketCreated(ServerSocket* server, const SocketWrapper& listenSocket) = 0; - virtual void onServerTimeout(ServerSocket* server, const SocketWrapper& listenSocket) = 0; - virtual void onSocketAccepted(ServerSocket* server, const SocketWrapper& listenSocket, std::unique_ptr clientSocket) = 0; + virtual void onServerSocketCreated(ServerSocket* server, const ServerSocketWrapper& listenSocket) = 0; + virtual void onServerTimeout(ServerSocket* server, const ServerSocketWrapper& listenSocket) = 0; + virtual void onSocketAccepted(ServerSocket* server, const ServerSocketWrapper& listenSocket, std::unique_ptr clientSocket) = 0; virtual void onServerException(ServerSocket* server, const std::system_error& error) = 0; }; diff --git a/include/iovi_space/network/server_socket/ServerSocketWrapper.h b/include/iovi_space/network/server_socket/ServerSocketWrapper.h index 3f55018..e7a8b01 100644 --- a/include/iovi_space/network/server_socket/ServerSocketWrapper.h +++ b/include/iovi_space/network/server_socket/ServerSocketWrapper.h @@ -5,4 +5,56 @@ #ifndef NETWORK_SERVERSOCKETWRAPPER_H #define NETWORK_SERVERSOCKETWRAPPER_H +#include +#include + +#ifdef _WIN32 + #include + #include + using SocketHandle = SOCKET; +constexpr SocketHandle INVALID_SOCKET_HANDLE = INVALID_SOCKET; +#else +#include +#include +#include +using SocketHandle = int; +constexpr SocketHandle INVALID_SOCKET_HANDLE = -1; +#endif + +namespace iovi_space::network { + + /** + * @brief RAII-обёртка над нативным сокетом. + * Автоматически закрывает сокет при разрушении. + */ + class ServerSocketWrapper { + public: + ServerSocketWrapper() : handle_(INVALID_SOCKET_HANDLE) {} + explicit ServerSocketWrapper(SocketHandle handle) : handle_(handle) {} + + // Запрет копирования, разрешение перемещения + ServerSocketWrapper(const ServerSocketWrapper&) = delete; + ServerSocketWrapper& operator=(const ServerSocketWrapper&) = delete; + ServerSocketWrapper(ServerSocketWrapper&& other) noexcept; + ServerSocketWrapper& operator=(ServerSocketWrapper&& other) noexcept; + + ~ServerSocketWrapper(); + + [[nodiscard]] bool isValid() const { return handle_ != INVALID_SOCKET_HANDLE; } + [[nodiscard]] SocketHandle get() const { return handle_; } + + // Сброс владения (для передачи сырого дескриптора) + [[nodiscard]] SocketHandle release(); + + // Утилиты + void setNonBlocking(bool enable); + void setReceiveTimeout(int milliseconds); + [[nodiscard]] std::string getPeerAddress() const; + + private: + SocketHandle handle_; + }; + +} // namespace iovi_space::network + #endif //NETWORK_SERVERSOCKETWRAPPER_H \ No newline at end of file diff --git a/src/ServerSocket.cpp b/src/ServerSocket.cpp new file mode 100644 index 0000000..25c4121 --- /dev/null +++ b/src/ServerSocket.cpp @@ -0,0 +1,125 @@ +#include "../include/iovi_space/network/server_socket/ServerSocket.h" + +#include + +#ifdef _WIN32 + #include + // Инициализация Winsock должна быть выполнена один раз в main() + // через WSAStartup(). Здесь предполагаем, что это уже сделано. + #define SOCKET_INIT() do {} while(0) +#else + #include + #include + #include +#endif + +namespace iovi_space::network { + +ServerSocket::ServerSocket(ServerSocketListener* listener, const std::string& name, int port, int timeout_ms) + : listener_(listener), name_(name), port_(port), timeout_ms_(timeout_ms) +{ + if (!listener_) { + throw std::invalid_argument("Listener cannot be null"); + } + worker_ = std::thread(&ServerSocket::run, this); +} + +ServerSocket::~ServerSocket() { + stop(); + if (worker_.joinable()) { + worker_.join(); + } +} + +void ServerSocket::stop() { + stop_requested_.store(true); + running_.store(false); +} + +void ServerSocket::run() { +#ifdef _WIN32 + // Примечание: в реальном проекте инициализация Winsock должна быть в main() + // WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData); +#endif + + listener_->onServerStart(this); + + try { + // Создаём сокет + SocketHandle sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == INVALID_SOCKET_HANDLE) { + throw std::system_error(GET_ERROR, std::system_category(), "socket() failed"); + } + + // Опция SO_REUSEADDR для быстрого перезапуска + int opt = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&opt), sizeof(opt)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +#endif + + // Привязка к адресу + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(static_cast(port_)); + + if (bind(sock, reinterpret_cast(&addr), sizeof(addr)) == -1) { + CLOSE_SOCKET(sock); + throw std::system_error(GET_ERROR, std::system_category(), "bind() failed on port " + std::to_string(port_)); + } + + // Переход в режим прослушивания + if (listen(sock, SOMAXCONN) == -1) { + CLOSE_SOCKET(sock); + throw std::system_error(GET_ERROR, std::system_category(), "listen() failed"); + } + + listenSocket_ = SocketWrapper(sock); + listener_->onServerSocketCreated(this, listenSocket_); + + running_.store(true); + + // Главный цикл + while (!stop_requested_.load()) { + // Используем select для реализации таймаута accept() + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket_.get(), &readfds); + + struct timeval timeout{}; + timeout.tv_sec = timeout_ms_ / 1000; + timeout.tv_usec = (timeout_ms_ % 1000) * 1000; + + int result = select(static_cast(listenSocket_.get()) + 1, &readfds, nullptr, nullptr, &timeout); + + if (stop_requested_.load()) break; + + if (result == 0) { + // Таймаут + listener_->onServerTimeout(this, listenSocket_); + continue; + } else if (result > 0 && FD_ISSET(listenSocket_.get(), &readfds)) { + // Есть новое подключение + SocketHandle clientSock = accept(listenSocket_.get(), nullptr, nullptr); + if (clientSock != INVALID_SOCKET_HANDLE) { + auto clientWrapper = std::make_unique(clientSock); + listener_->onSocketAccepted(this, listenSocket_, std::move(clientWrapper)); + } + // Если accept не удался - игнорируем и продолжаем (как в оригинале) + } + // result < 0 означает ошибку select, но для простоты продолжаем цикл + } + + } catch (const std::system_error& e) { + listener_->onServerException(this, e); + } catch (...) { + listener_->onServerException(this, std::system_error(std::error_code(), std::generic_category(), "Unknown exception")); + } + + listener_->onServerStop(this); + running_.store(false); +} + +} // namespace iovi_space::network \ No newline at end of file diff --git a/src/ServerSocketWrapper.cpp b/src/ServerSocketWrapper.cpp new file mode 100644 index 0000000..939a9a8 --- /dev/null +++ b/src/ServerSocketWrapper.cpp @@ -0,0 +1,97 @@ +#include "../include/iovi_space/network/server_socket/ServerSocketWrapper.h" +#include +#include +#include + +#ifdef _WIN32 + #include + #define CLOSE_SOCKET closesocket + #define GET_ERROR WSAGetLastError() +#else + #include + #include + #define CLOSE_SOCKET close + #define GET_ERROR errno +#endif + +namespace iovi_space::network { + +// Move constructor +ServerSocketWrapper::ServerSocketWrapper(ServerSocketWrapper&& other) noexcept : handle_(other.handle_) { + other.handle_ = INVALID_SOCKET_HANDLE; +} + +// Move assignment +ServerSocketWrapper& ServerSocketWrapper::operator=(ServerSocketWrapper&& other) noexcept { + if (this != &other) { + if (isValid()) { + CLOSE_SOCKET(handle_); + } + handle_ = other.handle_; + other.handle_ = INVALID_SOCKET_HANDLE; + } + return *this; +} + +// Destructor +ServerSocketWrapper::~ServerSocketWrapper() { + if (isValid()) { + CLOSE_SOCKET(handle_); + } +#ifdef _WIN32 + // Примечание: WSACleanup() должен вызываться в конце работы приложения (main) +#endif +} + +SocketHandle ServerSocketWrapper::release() { + SocketHandle tmp = handle_; + handle_ = INVALID_SOCKET_HANDLE; + return tmp; +} + +void ServerSocketWrapper::setNonBlocking(bool enable) { +#ifdef _WIN32 + u_long mode = enable ? 1 : 0; + if (ioctlsocket(handle_, FIONBIO, &mode) != 0) { + throw std::system_error(GET_ERROR, std::system_category(), "ioctlsocket failed"); + } +#else + int flags = fcntl(handle_, F_GETFL, 0); + if (flags == -1) flags = 0; + if (enable) flags |= O_NONBLOCK; + else flags &= ~O_NONBLOCK; + if (fcntl(handle_, F_SETFL, flags) == -1) { + throw std::system_error(errno, std::system_category(), "fcntl failed"); + } +#endif +} + +void ServerSocketWrapper::setReceiveTimeout(int milliseconds) { +#ifdef _WIN32 + DWORD timeout = static_cast(milliseconds); + if (setsockopt(handle_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast(&timeout), sizeof(timeout)) != 0) { + throw std::system_error(GET_ERROR, std::system_category(), "setsockopt SO_RCVTIMEO failed"); + } +#else + struct timeval timeout{}; + timeout.tv_sec = milliseconds / 1000; + timeout.tv_usec = (milliseconds % 1000) * 1000; + if (setsockopt(handle_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) { + throw std::system_error(errno, std::system_category(), "setsockopt SO_RCVTIMEO failed"); + } +#endif +} + +std::string ServerSocketWrapper::getPeerAddress() const { + sockaddr_in addr{}; + socklen_t addrLen = sizeof(addr); + if (getpeername(handle_, reinterpret_cast(&addr), &addrLen) == 0) { + char ipStr[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &addr.sin_addr, ipStr, sizeof(ipStr))) { + return std::string(ipStr) + ":" + std::to_string(ntohs(addr.sin_port)); + } + } + return "unknown"; +} + +} // namespace iovi_space::network \ No newline at end of file diff --git a/src/iovi_space/network/server_socket/ServerSocket.cpp b/src/iovi_space/network/server_socket/ServerSocket.cpp deleted file mode 100644 index 6cb37f1..0000000 --- a/src/iovi_space/network/server_socket/ServerSocket.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "../../../../include/iovi_space/network/server_socket/ServerSocket.h" - -#include - -void hello() { - std::cout << "Hello, World!" << std::endl; -} \ No newline at end of file diff --git a/src/iovi_space/network/server_socket/ServerSocketWrapper.cpp b/src/iovi_space/network/server_socket/ServerSocketWrapper.cpp deleted file mode 100644 index f6e0fc7..0000000 --- a/src/iovi_space/network/server_socket/ServerSocketWrapper.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// -// Created by ovchinnikov-ii on 5/28/26. -// \ No newline at end of file