/
yaseeeeechka
/
CenterControl
Обзор
Документация
Войти
/
yaseeeeechka
/
CenterControl
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
CenterControl_TCP/server.cpp
678 строк
31 KB
Yaroslava
New structure of Repo. TCP impl
11 дек 2025, 21:12
11 дек 2025, 21:12
6fdf9d7
Код
Авторство
О чём код?
// server_win.cpp — TCP-сервер: одна точка + управление WSAD // Протокол ответа: // STATE 1\n // x y\n // END\n // Команды (регистр не важен): "W", "A", "S", "D", "STATE", "QUIT" #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 <cmath> #include "MsgProtocol.h" #include "SqliteDB.h" using SOCKET = int; using BOOL = int; constexpr int SOCKET_ERROR = -1; constexpr int INVALID_SOCKET = -1; #pragma comment(lib, "ws2_32.lib") #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 "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 int kMaxConnections = 5; constexpr size_t kBuffMaxSize = 1024; static bool RecvAll(SOCKET s, char *buf, int len) { int total = 0; while (total < len) { int bytes = recv(s, buf + total, len - total, 0); if (bytes <= 0) return false; total += bytes; } return true; } class Session { public: explicit Session(SOCKET client_socket) : client_socket_(client_socket), unit_state_(SpawnLocation::Bottom) { std::cout << "[Session] Client connected\n"; Run(); } ~Session() { if (!logged_in_user_.empty()) { LoggedUsers::Instance().Logout(logged_in_user_); RoomsManager::Instance().DetachUser(logged_in_user_); } if (client_socket_ != INVALID_SOCKET) { #ifdef _WIN32 closesocket(client_socket_); #elif __linux__ close(client_socket_); #endif } } private: void Run() { while (true) { char header[6]; if (!RecvAll(client_socket_, header, sizeof(header))) { std::cout << "[Session] Client disconnected\n"; break; } uint32_t payload_size; std::memcpy(&payload_size, &header[2], sizeof(payload_size)); std::vector<char> packet; packet.insert(packet.end(), header, header + sizeof(header)); std::vector<char> payload(payload_size); if (!RecvAll(client_socket_, payload.data(), payload_size)) { std::cout << "[Session] Client disconnected (payload)\n"; break; } packet.insert(packet.end(), payload.begin(), payload.end()); 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(); sendAll(client_socket_, raw.data(), raw.size()); 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()); logged_in_user_ = loginNorm; if (!AuthorizeUser(loginNorm, ap->GetPassword())) { result_status = STATUS::RET_ERROR; } else if (!LoggedUsers::Instance().TryLogin(loginNorm)) { result_status = STATUS::RET_ALREADY_LOGGED_IN; } 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(); sendAll(client_socket_, raw.data(), raw.size()); 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>(); // установить action=LIST replyP->CreateFromRawData(RoomsPayload::ListRequest()->GetRawPayload()); replyP->SetRooms(std::move(infos)); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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) { // Игра ещё не запущена — действуем по старым правилам: максимум 4 игроков 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 { // Игра уже идёт — пользователь будет наблюдателем. // В комнату как участника НЕ добавляем: он не должен числиться в списке игроков комнаты. // Достаточно вернуть статус успеха, чтобы клиент мог переключиться в GameScreen // и опрашивать состояние игры по имени комнаты. st = STATUS::RET_SUCCESS; } } auto replyP = RoomsPayload::JoinRequest(roomName); replyP->SetStatus(st); MsgProtocol reply(WINDOWS::ROOMS, replyP); auto raw = reply.GetRawPayload(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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()); } // После отправки — вернуть последние 10 сообщений этой комнаты/глобала 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); 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(); sendAll(client_socket_, raw.data(), raw.size()); 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; // пусть тоже видит таймер/GO } } 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(); sendAll(client_socket_, raw.data(), raw.size()); 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(); sendAll(client_socket_, raw.data(), raw.size()); break; } case GAME_ACTION::MOVE: { if (!logged_in_user_.empty()) { // Направление, которое пришло с клиента auto dirRaw = gp->GetMoveDirection(); // uint8_t 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(); sendAll(client_socket_, raw.data(), raw.size()); 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(); sendAll(client_socket_, raw.data(), raw.size()); 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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(); sendAll(client_socket_, raw.data(), raw.size()); } 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: SOCKET client_socket_; UnitState unit_state_; std::string logged_in_user_{}; // пусто, пока не авторизован }; 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_STREAM, IPPROTO_TCP); if (server_socket_ == INVALID_SOCKET) { #ifdef _WIN32 WSACleanup(); #endif throw std::runtime_error("[Server] Creating 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"); } ret = listen(server_socket_, kMaxConnections); if (ret == -1) { #ifdef _WIN32 closesocket(server_socket_); WSACleanup(); #elif __linux__ close(server_socket_); #endif throw std::runtime_error("[Server] listen() error"); } std::cout << "[Server] Listening on 0.0.0.0:" << port_ << "\n"; } ~Server() { for (auto &t: threads_) { if (t.joinable()) t.join(); // дождаться всех потоков } 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) { SOCKET client_socket = accept(server_socket_, nullptr, nullptr); if (client_socket == INVALID_SOCKET) { std::cerr << "[Server] accept() failed\n"; continue; } std::cout << "[Server] New client connected\n"; // создаем поток для сессии threads_.emplace_back([client_socket]() { try { Session session{client_socket}; // Session::Run вызывается в конструкторе } catch (const std::exception &e) { std::cerr << "[Server] Session error: " << e.what() << "\n"; } }); } } private: uint16_t port_; SOCKET server_socket_{INVALID_SOCKET}; std::vector<std::thread> threads_; }; 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; }