/
yaseeeeechka
/
CenterControl
Обзор
Документация
Войти
/
yaseeeeechka
/
CenterControl
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CenterControl_UDP/server.cpp
673 строки
27 KB
Yaroslava
Add UDP impl
11 дек 2025, 21:44
11 дек 2025, 21:44
b29a632
Код
Авторство
О чём код?
// server.cpp — UDP-сервер для игры/чатов/комнат #define _WINSOCK_DEPRECATED_NO_WARNINGS #ifdef __linux__ #include <SFML/Graphics.hpp> #include <string> #include <iostream> #include <vector> #include <functional> #include <arpa/inet.h> #include <unistd.h> #include <thread> #include <memory> #include <set> #include <mutex> #include <unordered_set> #include <unordered_map> #include <cmath> #include "MsgProtocol.h" #include "SqliteDB.h" using SOCKET = int; using BOOL = int; constexpr int SOCKET_ERROR = -1; constexpr int INVALID_SOCKET = -1; #else #include <WinSock2.h> #include <SFML/Graphics.hpp> #include <string> #include <sstream> #include <iostream> #include <vector> #include <functional> #include <thread> #include <memory> #include <set> #include <mutex> #include <unordered_set> #include <unordered_map> #include "MsgProtocol.h" #include "SqliteDB.h" #pragma comment(lib, "ws2_32.lib") #endif #include "GameFieldConfig.h" #include "server/UnitState.h" #include "server/ChatManager.h" #include "server/RoomManager.h" #include "server/GameManager.h" #include "server/LoggedUsers.h" #include "server/Utils.h" constexpr size_t kBuffMaxSize = 2048; //==================== Ключ клиента для UDP ==================== struct ClientKey { uint32_t addr; uint16_t port; bool operator==(const ClientKey& other) const { return addr == other.addr && port == other.port; } }; struct ClientKeyHash { std::size_t operator()(const ClientKey& k) const noexcept { return static_cast<std::size_t>(k.addr) ^ (static_cast<std::size_t>(k.port) << 16); } }; //==================== Session (серверная, UDP) ==================== class Session { public: Session(SOCKET server_socket, const sockaddr_in& client_addr) : server_socket_(server_socket), client_addr_(client_addr), unit_state_(SpawnLocation::Bottom) { std::cout << "[Session] UDP client session created\n"; } ~Session() { if (!logged_in_user_.empty()) { LoggedUsers::Instance().Logout(logged_in_user_); RoomsManager::Instance().DetachUser(logged_in_user_); } std::cout << "[Session] Destroyed\n"; } void HandlePacket(const std::vector<char>& packet) { try { MsgProtocol msg; msg.CreateFromRawData(packet); switch (msg.GetWindowType()) { case WINDOWS::REGISTER: { auto ap = std::dynamic_pointer_cast<AuthPayload>(msg.GetPayload()); if (!ap) { std::cerr << "[Session] REGISTER: bad payload\n"; break; } STATUS result_status = RegisterUser(NormalizeUsername(ap->GetLogin()), ap->GetPassword()) ? STATUS::RET_SUCCESS : STATUS::RET_ERROR; if (result_status == STATUS::RET_SUCCESS) { LoggedUsers::Instance().TryLogin(NormalizeUsername(ap->GetLogin())); logged_in_user_ = NormalizeUsername(ap->GetLogin()); } auto reply_payload = std::make_shared<AuthPayload>(ap->GetLogin(), "", result_status); MsgProtocol reply(WINDOWS::REGISTER, reply_payload); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case WINDOWS::AUTH: { auto ap = std::dynamic_pointer_cast<AuthPayload>(msg.GetPayload()); if (!ap) { std::cerr << "[Session] AUTH: bad payload\n"; break; } STATUS result_status = STATUS::RET_ERROR; const std::string loginNorm = NormalizeUsername(ap->GetLogin()); // 1. Проверяем логин/пароль if (!AuthorizeUser(loginNorm, ap->GetPassword())) { result_status = STATUS::RET_ERROR; } else { // 2. Сбрасываем возможную "подвисшую" старую сессию LoggedUsers::Instance().Logout(loginNorm); RoomsManager::Instance().DetachUser(loginNorm); // 3. Привязываем логин к текущей Session logged_in_user_ = loginNorm; if (!LoggedUsers::Instance().TryLogin(loginNorm)) { // Теоретически не должно случиться, но на всякий случай result_status = STATUS::RET_ERROR; } else { result_status = STATUS::RET_SUCCESS; } } auto reply_payload = std::make_shared<AuthPayload>(ap->GetLogin(), "", result_status); MsgProtocol reply(WINDOWS::AUTH, reply_payload); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case WINDOWS::ROOMS: { auto rpay = std::dynamic_pointer_cast<RoomsPayload>(msg.GetPayload()); if (!rpay) { std::cerr << "[Session] ROOMS: bad payload\n"; break; } if (rpay->GetAction() == ROOM_ACTION::LIST_ROOMS) { auto infos = RoomsManager::Instance().GetRoomInfos(); auto replyP = std::make_shared<RoomsPayload>(); replyP->CreateFromRawData(RoomsPayload::ListRequest()->GetRawPayload()); replyP->SetRooms(std::move(infos)); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (rpay->GetAction() == ROOM_ACTION::CREATE_ROOM) { STATUS st = STATUS::RET_ERROR; const auto& rn = rpay->GetRoomName(); if (!rn.empty()) st = RoomsManager::Instance().Create(rn) ? STATUS::RET_SUCCESS : STATUS::RET_ERROR; auto replyP = RoomsPayload::CreateRequest(rpay->GetRoomName()); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (rpay->GetAction() == ROOM_ACTION::DELETE_ROOM) { STATUS st = STATUS::RET_ERROR; const auto& rn = rpay->GetRoomName(); if (!rn.empty()) { st = RoomsManager::Instance().DeleteIfEmpty(rn) ? STATUS::RET_SUCCESS : STATUS::RET_ERROR; } auto replyP = RoomsPayload::DeleteRequest(rn); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (rpay->GetAction() == ROOM_ACTION::JOIN_ROOM) { STATUS st = STATUS::RET_ERROR; const std::string roomName = rpay->GetRoomName(); if (!logged_in_user_.empty()) { bool gameExists = GameManager::Instance().HasGame(roomName); auto players_before = RoomsManager::Instance().GetPlayers(roomName); if (!gameExists) { if (players_before.size() >= 4) { st = STATUS::RET_ROOM_FULL; } else { bool ok = RoomsManager::Instance().Join(logged_in_user_, roomName); st = ok ? STATUS::RET_SUCCESS : STATUS::RET_ERROR; if (ok) { auto players_after = RoomsManager::Instance().GetPlayers(roomName); if (players_after.size() == 4) { GameManager::Instance().StartGame(roomName, players_after); } } } } else { st = STATUS::RET_SUCCESS; // зритель } } auto replyP = RoomsPayload::JoinRequest(roomName); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (rpay->GetAction() == ROOM_ACTION::LEAVE_ROOM) { STATUS st = STATUS::RET_ERROR; if (!logged_in_user_.empty()) { st = RoomsManager::Instance().Leave(logged_in_user_, rpay->GetRoomName()) ? STATUS::RET_SUCCESS : STATUS::RET_ERROR; } auto replyP = RoomsPayload::LeaveRequest(rpay->GetRoomName()); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (rpay->GetAction() == ROOM_ACTION::LIST_PLAYERS) { auto players = RoomsManager::Instance().GetPlayers(rpay->GetRoomName()); auto replyP = RoomsPayload::ListPlayersRequest(rpay->GetRoomName()); replyP->SetPlayers(std::move(players)); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } break; } case WINDOWS::CHAT: { auto cp = std::dynamic_pointer_cast<ChatPayload>(msg.GetPayload()); if (!cp) { std::cerr << "[Session] CHAT: bad payload\n"; break; } std::string roomName = cp->GetRoom(); if (cp->GetAction() == CHAT_ACTION::SEND_MESSAGE) { if (!logged_in_user_.empty()) { ChatManager::Instance().AddMessage(roomName, logged_in_user_, cp->GetText()); } auto last = ChatManager::Instance().GetLastMessages(roomName, 10); auto replyP = std::make_shared<ChatPayload>(); replyP->SetAction(CHAT_ACTION::HISTORY); replyP->SetRoom(roomName); replyP->SetMessages(std::move(last)); MsgProtocol reply(WINDOWS::CHAT, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } else if (cp->GetAction() == CHAT_ACTION::HISTORY) { auto last = ChatManager::Instance().GetLastMessages(roomName, 10); auto replyP = std::make_shared<ChatPayload>(); replyP->SetAction(CHAT_ACTION::HISTORY); replyP->SetRoom(roomName); replyP->SetMessages(std::move(last)); MsgProtocol reply(WINDOWS::CHAT, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); } break; } case WINDOWS::GAME: { auto gp = std::dynamic_pointer_cast<GamePayload>(msg.GetPayload()); if (!gp) { std::cerr << "[Session] GAME: bad payload\n"; break; } STATUS st = STATUS::RET_ERROR; uint8_t spawnIdx = 0; uint8_t countdown = 255; const std::string room = gp->GetRoomName(); switch (gp->GetAction()) { case GAME_ACTION::START: { if (!logged_in_user_.empty()) { auto players = RoomsManager::Instance().GetPlayers(room); bool ok = GameManager::Instance().StartGame(room, players); if (ok) { st = STATUS::RET_SUCCESS; auto info = GameManager::Instance().QueryState(room, logged_in_user_); if (info.inGame) { spawnIdx = SpawnLocationToIndex(info.spawn); countdown = info.countdown; } } else { st = STATUS::RET_ERROR; } } auto replyP = GamePayload::StartRequest(room); replyP->SetStatus(st); replyP->SetSpawnIndex(spawnIdx); replyP->SetCountdown(countdown); auto playersState = GameManager::Instance().GetPlayersState(room); replyP->SetPlayers(std::move(playersState)); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case GAME_ACTION::READY: { if (!logged_in_user_.empty()) { GameManager::Instance().PlayerReady(room, logged_in_user_); auto info = GameManager::Instance().QueryState(room, logged_in_user_); if (!info.gameExists) { st = STATUS::RET_ERROR; } else { st = info.inGame ? STATUS::RET_SUCCESS : STATUS::RET_ALREADY_LOGGED_IN; spawnIdx = SpawnLocationToIndex(info.spawn); countdown = info.countdown; } } auto replyP = GamePayload::ReadyRequest(room); replyP->SetStatus(st); replyP->SetSpawnIndex(spawnIdx); replyP->SetCountdown(countdown); auto playersState = GameManager::Instance().GetPlayersState(room); replyP->SetPlayers(std::move(playersState)); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case GAME_ACTION::QUERY_STATE: { if (!logged_in_user_.empty()) { auto info = GameManager::Instance().QueryState(room, logged_in_user_); if (!info.gameExists) { st = STATUS::RET_ERROR; } else if (info.inGame) { st = STATUS::RET_SUCCESS; spawnIdx = SpawnLocationToIndex(info.spawn); countdown = info.countdown; } else { st = STATUS::RET_ALREADY_LOGGED_IN; countdown = info.countdown; } } auto replyP = GamePayload::QueryStateRequest(room); replyP->SetStatus(st); replyP->SetSpawnIndex(spawnIdx); replyP->SetCountdown(countdown); auto playersState = GameManager::Instance().GetPlayersState(room); replyP->SetPlayers(std::move(playersState)); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case GAME_ACTION::LEAVE: { if (!logged_in_user_.empty()) { GameManager::Instance().LeaveGame(room, logged_in_user_); st = STATUS::RET_SUCCESS; } auto replyP = GamePayload::LeaveRequest(room); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); // Сессию не уничтожаем — игрок остаётся залогинен, как в TCP-версии break; } case GAME_ACTION::MOVE: { if (!logged_in_user_.empty()) { auto dirRaw = gp->GetMoveDirection(); MOVE_DIR dir = static_cast<MOVE_DIR>(dirRaw); GameManager::Instance().MovePlayer(room, logged_in_user_, dir); auto info = GameManager::Instance().QueryState(room, logged_in_user_); if (!info.gameExists) { st = STATUS::RET_ERROR; } else { st = info.inGame ? STATUS::RET_SUCCESS : STATUS::RET_ALREADY_LOGGED_IN; spawnIdx = SpawnLocationToIndex(info.spawn); countdown = info.countdown; } } auto replyP = GamePayload::MoveRequest(room, gp->GetMoveDirection()); replyP->SetStatus(st); replyP->SetSpawnIndex(spawnIdx); replyP->SetCountdown(countdown); auto playersState = GameManager::Instance().GetPlayersState(room); replyP->SetPlayers(std::move(playersState)); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } case GAME_ACTION::PUSH: { if (!logged_in_user_.empty()) { GameManager::Instance().Push(room, logged_in_user_); auto info = GameManager::Instance().QueryState(room, logged_in_user_); if (!info.gameExists) { st = STATUS::RET_ERROR; } else { st = info.inGame ? STATUS::RET_SUCCESS : STATUS::RET_ALREADY_LOGGED_IN; spawnIdx = SpawnLocationToIndex(info.spawn); countdown = info.countdown; } } auto replyP = GamePayload::PushRequest(room); replyP->SetStatus(st); replyP->SetSpawnIndex(spawnIdx); replyP->SetCountdown(countdown); auto playersState = GameManager::Instance().GetPlayersState(room); replyP->SetPlayers(std::move(playersState)); MsgProtocol reply(WINDOWS::GAME, replyP); auto raw = reply.GetRawPayload(); SendToClient(raw); break; } default: break; } break; } case WINDOWS::SCORES: { auto p = std::dynamic_pointer_cast<ScoresPayload>(msg.GetPayload()); if (!p) { std::cerr << "[Session] SCORES: bad payload\n"; break; } uint16_t limit = p->GetLimit(); std::vector<ScoreDTO> scores; if (limit == 0) { ScoreDTO dto; dto.user = logged_in_user_; dto.score = 0; if (!logged_in_user_.empty()) { dto.score = GetUserStars(logged_in_user_); } scores.push_back(dto); auto respPayload = ScoresPayload::TopRequest(0); respPayload->SetScores(std::move(scores)); MsgProtocol resp(WINDOWS::SCORES, respPayload); auto raw = resp.GetRawPayload(); SendToClient(raw); } else { if (limit == 0) limit = 100; std::vector<ScoreDTO> top = GetTopScores(limit); auto respPayload = ScoresPayload::TopRequest(limit); respPayload->SetScores(std::move(top)); MsgProtocol resp(WINDOWS::SCORES, respPayload); auto raw = resp.GetRawPayload(); SendToClient(raw); } break; } default: std::cerr << "[Session] Unsupported window type: " << (int)msg.GetWindowType() << "\n"; break; } } catch (const std::exception& e) { std::cerr << "[Session] Protocol error: " << e.what() << std::endl; } } private: void SendToClient(const std::vector<char>& data) { #ifdef _WIN32 int addr_len = sizeof(client_addr_); #else socklen_t addr_len = sizeof(client_addr_); #endif int sent = sendto(server_socket_, data.data(), (int)data.size(), 0, (sockaddr*)&client_addr_, addr_len); if (sent <= 0) { std::cerr << "[Session] sendto() failed\n"; } } private: SOCKET server_socket_; sockaddr_in client_addr_; UnitState unit_state_; std::string logged_in_user_; }; //==================== Server (UDP) ==================== class Server { public: explicit Server(uint16_t port) : port_(port) { #ifdef _WIN32 WSADATA wsa{}; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { throw std::runtime_error("[Server] WSAStartup failed"); } #endif server_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (server_socket_ == INVALID_SOCKET) { #ifdef _WIN32 WSACleanup(); #endif throw std::runtime_error("[Server] Creating UDP socket error"); } BOOL opt = true; int ret = setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)); if (ret == -1) { throw std::runtime_error("[Server] Setsockopt() error"); } sockaddr_in server_addr{}; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port_); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); ret = bind(server_socket_, (sockaddr*)&server_addr, sizeof(server_addr)); if (ret == -1) { #ifdef _WIN32 closesocket(server_socket_); WSACleanup(); #elif __linux__ close(server_socket_); #endif throw std::runtime_error("[Server] Bind() error"); } std::cout << "[Server] UDP listening on 0.0.0.0:" << port_ << "\n"; } ~Server() { if (server_socket_ != INVALID_SOCKET) { #ifdef _WIN32 closesocket(server_socket_); #elif __linux__ close(server_socket_); #endif server_socket_ = INVALID_SOCKET; } #ifdef _WIN32 WSACleanup(); #endif } void Run() { while (true) { char buf[kBuffMaxSize]; sockaddr_in client_addr{}; #ifdef _WIN32 int addr_len = sizeof(client_addr); #else socklen_t addr_len = sizeof(client_addr); #endif int received = recvfrom(server_socket_, buf, sizeof(buf), 0, (sockaddr*)&client_addr, &addr_len); if (received <= 0) { std::cerr << "[Server] recvfrom() failed or 0 bytes\n"; continue; } ClientKey key{ client_addr.sin_addr.s_addr, client_addr.sin_port }; auto it = sessions_.find(key); if (it == sessions_.end()) { auto s = std::make_unique<Session>(server_socket_, client_addr); it = sessions_.emplace(key, std::move(s)).first; std::cout << "[Server] New UDP client session stored\n"; } std::vector<char> packet(buf, buf + received); it->second->HandlePacket(packet); } } private: uint16_t port_; SOCKET server_socket_{INVALID_SOCKET}; std::unordered_map<ClientKey, std::unique_ptr<Session>, ClientKeyHash> sessions_; }; //==================== main ==================== int main(int argc, char** argv) { uint16_t port = 5000; if (!InitDatabase()) { std::cerr << "Не удалось инициализировать базу данных!\n"; return 1; } try { Server srv{port}; srv.Run(); } catch (const std::exception& e) { std::cerr << "Fatal error: " << e.what() << "\n"; CloseDatabase(); return 1; } CloseDatabase(); return 0; }