#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