vibes
This commit is contained in:
+31
-7
@@ -1,11 +1,35 @@
|
|||||||
cmake_minimum_required(VERSION 4.1)
|
cmake_minimum_required(VERSION 3.28)
|
||||||
project(Network)
|
project(iovi_space_network LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
add_library(Network STATIC
|
# Исходники и заголовки
|
||||||
src/iovi_space/network/server_socket/ServerSocket.cpp
|
set(HEADERS
|
||||||
include/iovi_space/network/server_socket/ServerSocket.h
|
include/iovi_space/network/server_socket/ServerSocketListener.h
|
||||||
include/iovi_space/network/server_socket/ServerSocketWrapper.h
|
include/iovi_space/network/server_socket/ServerSocketWrapper.h
|
||||||
src/iovi_space/network/server_socket/ServerSocketWrapper.cpp
|
include/iovi_space/network/server_socket/ServerSocket.h
|
||||||
src/iovi_space/network/server_socket/ServerSocketListener.h)
|
)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/ServerSocketWrapper.cpp
|
||||||
|
src/ServerSocket.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаём статическую библиотеку
|
||||||
|
add_library(iovi_space_network STATIC ${SOURCES} ${HEADERS})
|
||||||
|
|
||||||
|
# Публичные заголовки для подключения извне
|
||||||
|
target_include_directories(iovi_space_network
|
||||||
|
PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
|
||||||
|
# Линковка системных библиотек для сокетов
|
||||||
|
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()
|
||||||
|
|||||||
@@ -1,6 +1,56 @@
|
|||||||
#ifndef NETWORK_LIBRARY_H
|
#ifndef NETWORK_LIBRARY_H
|
||||||
#define NETWORK_LIBRARY_H
|
#define NETWORK_LIBRARY_H
|
||||||
|
|
||||||
void hello();
|
#pragma once
|
||||||
|
#include "ServerSocketListener.h"
|
||||||
|
#include "ServerSocketWrapper.h"
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
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<bool> running_{false};
|
||||||
|
std::atomic<bool> stop_requested_{false};
|
||||||
|
|
||||||
|
ServerSocketWrapper listenSocket_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace iovi_space::network
|
||||||
|
|
||||||
#endif // NETWORK_LIBRARY_H
|
#endif // NETWORK_LIBRARY_H
|
||||||
+4
-4
@@ -12,7 +12,7 @@ namespace iovi_space::network {
|
|||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class ServerSocket;
|
class ServerSocket;
|
||||||
class SocketWrapper;
|
class ServerSocketWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Абстрактный интерфейс слушателя событий сервера.
|
* @brief Абстрактный интерфейс слушателя событий сервера.
|
||||||
@@ -24,9 +24,9 @@ namespace iovi_space::network {
|
|||||||
|
|
||||||
virtual void onServerStart(ServerSocket* server) = 0;
|
virtual void onServerStart(ServerSocket* server) = 0;
|
||||||
virtual void onServerStop(ServerSocket* server) = 0;
|
virtual void onServerStop(ServerSocket* server) = 0;
|
||||||
virtual void onServerSocketCreated(ServerSocket* server, const SocketWrapper& listenSocket) = 0;
|
virtual void onServerSocketCreated(ServerSocket* server, const ServerSocketWrapper& listenSocket) = 0;
|
||||||
virtual void onServerTimeout(ServerSocket* server, const SocketWrapper& listenSocket) = 0;
|
virtual void onServerTimeout(ServerSocket* server, const ServerSocketWrapper& listenSocket) = 0;
|
||||||
virtual void onSocketAccepted(ServerSocket* server, const SocketWrapper& listenSocket, std::unique_ptr<SocketWrapper> clientSocket) = 0;
|
virtual void onSocketAccepted(ServerSocket* server, const ServerSocketWrapper& listenSocket, std::unique_ptr<ServerSocketWrapper> clientSocket) = 0;
|
||||||
virtual void onServerException(ServerSocket* server, const std::system_error& error) = 0;
|
virtual void onServerException(ServerSocket* server, const std::system_error& error) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5,4 +5,56 @@
|
|||||||
#ifndef NETWORK_SERVERSOCKETWRAPPER_H
|
#ifndef NETWORK_SERVERSOCKETWRAPPER_H
|
||||||
#define NETWORK_SERVERSOCKETWRAPPER_H
|
#define NETWORK_SERVERSOCKETWRAPPER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
using SocketHandle = SOCKET;
|
||||||
|
constexpr SocketHandle INVALID_SOCKET_HANDLE = INVALID_SOCKET;
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
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
|
#endif //NETWORK_SERVERSOCKETWRAPPER_H
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#include "../include/iovi_space/network/server_socket/ServerSocket.h"
|
||||||
|
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
// Инициализация Winsock должна быть выполнена один раз в main()
|
||||||
|
// через WSAStartup(). Здесь предполагаем, что это уже сделано.
|
||||||
|
#define SOCKET_INIT() do {} while(0)
|
||||||
|
#else
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#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<const char*>(&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<uint16_t>(port_));
|
||||||
|
|
||||||
|
if (bind(sock, reinterpret_cast<sockaddr*>(&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<int>(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<SocketWrapper>(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
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include "../include/iovi_space/network/server_socket/ServerSocketWrapper.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <system_error>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#define CLOSE_SOCKET closesocket
|
||||||
|
#define GET_ERROR WSAGetLastError()
|
||||||
|
#else
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#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<DWORD>(milliseconds);
|
||||||
|
if (setsockopt(handle_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&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<sockaddr*>(&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
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#include "../../../../include/iovi_space/network/server_socket/ServerSocket.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
void hello() {
|
|
||||||
std::cout << "Hello, World!" << std::endl;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by ovchinnikov-ii on 5/28/26.
|
|
||||||
//
|
|
||||||
Reference in New Issue
Block a user