From 17013f70f512e7d898fac3f6580f7fb52b71b36c Mon Sep 17 00:00:00 2001 From: bumpsoo Date: Wed, 4 Feb 2026 13:58:58 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A4=91=EB=B3=B5=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=EC=A7=80=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/Client.cpp | 37 +++++++++++++++++++++++++++++++--- include/Client.h | 2 +- include/Packet.h | 7 +++++++ include/SessionManager.h | 1 + server/PacketHandler.cpp | 42 ++++++++++++++++++++++++++++++++++++--- server/SessionManager.cpp | 20 +++++++++++++++++++ 6 files changed, 102 insertions(+), 7 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 3ee0b2c..9a2af0c 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -33,13 +33,38 @@ bool BaseClient::ReceiveHeader(PacketHeader &header) { } } -void BaseClient::Login(const std::string &nickname) { +bool BaseClient::Login(const std::string &nickname) { PKT_CS_Login loginPkt; std::memset(loginPkt.nickname, 0, sizeof(loginPkt.nickname)); std::strncpy(loginPkt.nickname, nickname.c_str(), sizeof(loginPkt.nickname) - 1); SendPacket(PacketID::Login, &loginPkt, sizeof(loginPkt)); std::cout << "로그인 요청 보냄: " << nickname << std::endl; + + // 로그인 결과 대기 + PacketHeader header; + if (!ReceiveHeader(header) || + header.id != static_cast(PacketID::SC_LoginResult)) { + std::cerr << "로그인 응답을 받지 못했습니다." << std::endl; + return false; + } + + PKT_SC_LoginResult res; + if (!ReceivePayload(res)) { + std::cerr << "로그인 응답 수신 실패" << std::endl; + return false; + } + + if (res.result == 1) { + std::cout << "로그인 성공!" << std::endl; + return true; + } else if (res.result == 0) { + std::cerr << "로그인 실패: 이미 접속 중인 유저입니다." << std::endl; + return false; + } else { + std::cerr << "로그인 실패: 알 수 없는 오류" << std::endl; + return false; + } } void BaseClient::HandleUpgradeResult() { @@ -68,7 +93,10 @@ void BaseClient::HandleSellResult() { void InteractiveClient::Run() { std::cout << "닉네임을 입력하세요: "; std::cin >> nickname_; - Login(nickname_); + if (!Login(nickname_)) { + std::cerr << "로그인에 실패하여 종료합니다." << std::endl; + return; + } while (true) { if (currentLevel_ == 0) { @@ -128,7 +156,10 @@ void ScenarioClient::Run() { if (!(is >> nickname_)) return; - Login(nickname_); + if (!Login(nickname_)) { + std::cerr << "로그인에 실패하여 종료합니다." << std::endl; + return; + } std::string line; while (is >> line) { diff --git a/include/Client.h b/include/Client.h index 34e2325..2ad893b 100644 --- a/include/Client.h +++ b/include/Client.h @@ -27,7 +27,7 @@ public: } } - void Login(const std::string &nickname); + bool Login(const std::string &nickname); virtual void Run() = 0; diff --git a/include/Packet.h b/include/Packet.h index dd65c84..9f8197e 100644 --- a/include/Packet.h +++ b/include/Packet.h @@ -22,6 +22,8 @@ enum class PacketID : uint16_t { Ping = 1, // 로그인 요청 (PKT_CS_Login) Login = 10, + // 로그인 결과 응답 (PKT_SC_LoginResult) + SC_LoginResult = 11, Chat = 20, // 검 키우기 관련 패킷 @@ -52,6 +54,11 @@ struct PKT_CS_Login { char nickname[32]; }; +struct PKT_SC_LoginResult { + // 0: 이미 접속 중, 1: 성공 + uint8_t result; +}; + struct PKT_SC_UpgradeResult { // 0: 파괴, 1: 성공, 2: 실패 uint8_t result; diff --git a/include/SessionManager.h b/include/SessionManager.h index 83a778d..82c6ed1 100644 --- a/include/SessionManager.h +++ b/include/SessionManager.h @@ -11,6 +11,7 @@ public: static SessionManager &GetInstance(); void Join(std::shared_ptr session); + bool TryJoin(std::shared_ptr session, const std::string &nickname); void Leave(std::shared_ptr session); void Broadcast(PacketHeader header, std::span body); diff --git a/server/PacketHandler.cpp b/server/PacketHandler.cpp index 74515f1..a02fe61 100644 --- a/server/PacketHandler.cpp +++ b/server/PacketHandler.cpp @@ -30,14 +30,50 @@ PacketHandler::HandlePacket(std::shared_ptr session, auto userData = co_await DatabaseManager::GetInstance().LoadUser(nickname); - session->SetNickname(userData.nickname); + // 골드와 검 레벨 설정 (닉네임은 TryJoin에서 설정) session->SetGold(userData.gold); session->SetSwordLevel(userData.swordLevel); - Logger::Log("클라이언트 로그인: ", nickname, " (Gold: ", session->GetGold(), + PKT_SC_LoginResult loginResult; + + // 원자적으로 중복 체크 및 세션 추가 + if (!SessionManager::GetInstance().TryJoin(session, userData.nickname)) { + // 이미 접속 중인 유저 + Logger::Log("중복 로그인 거부: ", nickname); + loginResult.result = 0; + + PacketHeader header; + header.id = static_cast(PacketID::SC_LoginResult); + header.size = sizeof(PKT_SC_LoginResult); + + std::vector buffer(sizeof(PacketHeader) + + sizeof(PKT_SC_LoginResult)); + std::memcpy(buffer.data(), &header, sizeof(PacketHeader)); + std::memcpy(buffer.data() + sizeof(PacketHeader), &loginResult, + sizeof(PKT_SC_LoginResult)); + + session->Send(buffer); + co_return; + } + + Logger::Log("클라이언트 로그인 성공: ", nickname, + " (Gold: ", session->GetGold(), ", Level: ", session->GetSwordLevel(), ")"); - SessionManager::GetInstance().Join(session); + // 로그인 성공 응답 + loginResult.result = 1; + + PacketHeader header; + header.id = static_cast(PacketID::SC_LoginResult); + header.size = sizeof(PKT_SC_LoginResult); + + std::vector buffer(sizeof(PacketHeader) + + sizeof(PKT_SC_LoginResult)); + std::memcpy(buffer.data(), &header, sizeof(PacketHeader)); + std::memcpy(buffer.data() + sizeof(PacketHeader), &loginResult, + sizeof(PKT_SC_LoginResult)); + + session->Send(buffer); } break; case PacketID::CS_UpgradeSword: { diff --git a/server/SessionManager.cpp b/server/SessionManager.cpp index 8a89d84..3f4e5be 100644 --- a/server/SessionManager.cpp +++ b/server/SessionManager.cpp @@ -21,6 +21,26 @@ void SessionManager::Leave(std::shared_ptr session) { } } +bool SessionManager::TryJoin(std::shared_ptr session, + const std::string &nickname) { + std::lock_guard lock(mutex_); + + // 중복 체크 + for (const auto &existing : sessions_) { + if (existing->GetNickname() == nickname) { + Logger::Log("이미 접속 중인 유저 로그인 시도: ", nickname); + return false; + } + } + + // 중복이 없으면 닉네임 설정 후 추가 + session->SetNickname(nickname); + sessions_.insert(session); + Logger::Log("클라이언트[", nickname, + "]가 입장했습니다. 총 세션 수: ", sessions_.size()); + return true; +} + void SessionManager::Broadcast(PacketHeader header, std::span body) { std::lock_guard lock(mutex_);