vibes
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user