DB로 Redis를 연동하여 로그인 작업을 구현
PacketManager의 Run() 함수 실행시 RedisManager 객체를 생성하여 Init() 및 Run() 함수 실행
Connect() 함수에서 Redis DB와 연동한 후 DB 작업을 수행하는 쓰레드를 생성하여 ProcessTask() 함수 실행
ProcessTask() 함수에서는 deque에 RequestTask가 있다면 해당 Task에 맞는 작업을 수행
- Login 로직
- [PacketManager] 패킷 처리 쓰레드의 ProcessPacket() 함수에서 수신된 패킷 데이터를 확인하여 패킷Id가 LOGIN_REQUEST일 경우 ProcessLogin() 함수 실행
- [PacketManager] ProcessLogin() 함수에서 서버의 최대 접속자 수 초과 여부, 중복 접속 요청 여부를 검사
- 접속자 수 초과일경우 해당하는 LOGIN_USER_USED_ALL_OBJ 에러 코드를 LOGIN_RESPONSE_PACKET에 넣어 전송
- 중복 접속일 경우 해당하는 LOGIN_USER_ALREADY 에러 코드를 LOGIN_RESPONSE_PACKET에 넣어 전송
- 중복이 아닐 경우 DB에 유저ID와 PW 확인 요청을 해야하므로 TaskId로 REQUEST_LOGIN을 가진 Task를 생성하여 RedisManger의 PsuhRequest() 함수를 호출해 deque에 저장
- [RedisManager] Task 처리 쓰레드에서 수신된 Task 데이터를 확인하여 TaskId가 REQUEST_LOGIN일 경우 ProcessLogin() 함수 실행
- [RedisManager] CredisConn 클래스(c++에서 redis를 사용하기 위해 활용, 내부적으로 GET / SET 등의 redisCmd를 호출하여 Redis의 기능을 구현함)의 get() 함수를 호출하여 UserID에 대한 value값이 있는지 확인
- value값이 존재한다면 입력받은 UserPW와 동일한지 판별, 아니라면 LOGIN_USER_INVALID_PW 에러 코드를 가진 Task를 생성하여 PushResponse() 함수로 deque에 저장
- value값이 존재하지 않는다면 유저 생성으로 취급하여 TaskId로 RESPONSE_LOGIN을 가진 Task를 생ㅅ헝하여 PushResponse() 함수로 deque에 저장
- [PacketManager] 패킷 처리 쓰레드의 ProcessPacket() 함수에서 TakeResponseTask() 함수로 Task를 가져와 ProcessLoginDBResult() 함수를 실행
- [PacketManager] Task의 Result를 확인
- 에러 코드가 설정되어있지 않다면 클라이언트로 로그인 성공 응답 패킷 전송, 추가로 UserManager에 등록되어있지 않은 ID라면 새롭게 등록
- 에러 코드가 설정되어있다면 클라이언트로 에러 코드에 해당하는 응답 패킷 전송
- RedisTaskDefine.h
- 기존 패킷과 비슷한 구조를 가진 Task 패킷을 정의
- RedisTask가 기존의 패킷 헤더에 해당
#pragma once
#include "ErrorCode.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "Packet.h"
enum class RedisTaskID : UINT16
{
INVALID = 0,
REQUEST_LOGIN = 1001,
RESPONSE_LOGIN = 1002
};
struct RedisTask
{
UINT32 UserIndex = 0;
UINT16 TaskId = 0;
UINT16 DataSize = 0;
char *pData = nullptr;
void Release()
{
if (pData != nullptr)
{
delete[] pData;
}
}
};
#pragma pack(push, 1)
struct RedisLoginReq
{
char UserID[MAX_USER_ID_LEN + 1];
char UserPW[MAX_USER_PW_LEN + 1];
};
struct RedisLoginRes
{
char UserID[MAX_USER_ID_LEN + 1];
UINT16 Result = (UINT16)ERROR_CODE::NONE;
};
#pragma pack(pop)
- RedisManager.h
- 각 함수들의 구성과 작동방식은 PacketManager와 거의 동일함
#pragma once
#include "RedisTaskDefine.h"
#include "ErrorCode.h"
#include "./thirdparty/CRedisConn.h"
#include <unordered_map>
#include <vector>
#include <deque>
#include <thread>
#include <mutex>
class RedisManager
{
private:
typedef void(RedisManager::* PROCESS_TASK_PACKET_FUNCTION)(UINT32, UINT16, char*);
std::unordered_map<int, PROCESS_TASK_PACKET_FUNCTION> mTaskFunctionDictionary;
RedisCpp::CRedisConn mConn;
bool mIsRunProcessThread = false;
std::vector<std::thread> mTaskThreads;
std::mutex mReqLock;
std::deque<RedisTask> mRequestTask;
std::mutex mResLock;
std::deque<RedisTask> mResponseTask;
bool Connect(std::string ip_, UINT16 port_)
{
if (mConn.connect(ip_, port_) == false)
{
std::cout << "Connect Error : " << mConn.getErrorStr() << std::endl;
return false;
}
else
{
std::cout << "Connect Success\n";
}
return true;
}
void PushResponse(RedisTask task_)
{
std::lock_guard<std::mutex> guard(mResLock);
mResponseTask.push_back(task_);
}
RedisTask DequeRequestTask()
{
std::lock_guard<std::mutex> guard(mReqLock);
if (mRequestTask.empty())
{
return RedisTask();
}
auto task = mRequestTask.front();
mRequestTask.pop_front();
return task;
}
void ProcessTask()
{
std::cout << "Redis Thread start...\n";
while (mIsRunProcessThread)
{
bool isIdle = true;
auto task = DequeRequestTask();
if (task.TaskId != 0)
{
isIdle = false;
ProcessTaskPacket(task.UserIndex, task.TaskId, task.DataSize, task.pData);
}
if (isIdle)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
void ProcessTaskPacket(const UINT32 userIndex_, const UINT16 taskId_, const UINT16 dataSize_, char* pData_)
{
auto iter = mTaskFunctionDictionary.find(taskId_);
if (iter != mTaskFunctionDictionary.end())
{
(this->*(iter->second))(userIndex_, dataSize_, pData_);
}
}
void ProcessLogin(UINT32 userIndex_, UINT16 dataSize_, char* pData_)
{
auto pRequest = (RedisLoginReq*)pData_;
RedisLoginRes bodyData;
bodyData.Result = (UINT16)ERROR_CODE::LOGIN_USER_INVALID_PW;
CopyMemory(bodyData.UserID, pRequest->UserID, (MAX_USER_ID_LEN + 1));
std::string value;
if (mConn.get(pRequest->UserID, value))
{
if (value.compare(pRequest->UserPW) == 0)
{
bodyData.Result = (UINT16)ERROR_CODE::NONE;
}
else
{
std::cout << "Wrong PassWord\n";
}
}
else
{
std::cout << "User " << pRequest->UserID << " Created\n";
mConn.set(pRequest->UserID, pRequest->UserPW);
bodyData.Result = (UINT16)ERROR_CODE::NONE;
}
RedisTask resTask;
resTask.UserIndex = userIndex_;
resTask.TaskId = (UINT16)RedisTaskID::RESPONSE_LOGIN;
resTask.DataSize = sizeof(RedisLoginRes);
resTask.pData = new char[resTask.DataSize];
CopyMemory(resTask.pData, (char*)&bodyData, resTask.DataSize);
PushResponse(resTask);
}
public:
RedisManager() = default;
~RedisManager() = default;
void Init()
{
mTaskFunctionDictionary[(int)RedisTaskID::REQUEST_LOGIN] = &RedisManager::ProcessLogin;
}
bool Run(std::string ip_, UINT16 port_, const UINT32 threadCount_)
{
if (Connect(ip_, port_) == false)
{
std::cout << "Redis Conncetion fail\n";
return false;
}
mIsRunProcessThread = true;
for (UINT32 i = 0; i < threadCount_; i++)
{
mTaskThreads.emplace_back([this]() { ProcessTask(); });
}
std::cout << "Redis is running...\n";
return true;
}
void End()
{
mIsRunProcessThread = false;
for (auto& thread : mTaskThreads)
{
if (thread.joinable())
{
thread.join();
}
}
}
void PushRequest(RedisTask task_)
{
std::lock_guard<std::mutex> guard(mReqLock);
mRequestTask.push_back(task_);
}
RedisTask TakeResponseTask()
{
std::lock_guard<std::mutex> guard(mResLock);
if (mResponseTask.empty())
{
return RedisTask();
}
auto task = mResponseTask.front();
mResponseTask.pop_front();
return task;
}
};
- PacketManager.h / cpp
- Run() 함수에 RedisManager 관련 처리를 하는 동작들을 추가
- ProcessPacket() 함수에 RedisManager의 Task를 확인하는 작업 추가, 패킷과 Task의 구조는 동일하기때문에 똑같이 ProcessRecvPacket() 함수로 처리 가능
- ProcessLogin() 함수는 로그인시 발생할 수 있는 경우들에 대해 적절히 처리하도록 구성
- ProcessLoginDBResult() 함수에서는 DB에 저장된 유저 정보를 확인
bool PacketManager::Run()
{
mRedisMgr = new RedisManager;
mRedisMgr->Init();
if (mRedisMgr->Run("127.0.0.1", 6379, 1) == false)
{
return false;
}
mIsRunProcessThread = true;
mProcessThread = std::thread([this]() { ProcessPacket(); });
return true;
}
void PacketManager::ProcessPacket()
{
while (mIsRunProcessThread)
{
bool isIdle = true;
auto packetData = DequePacketData();
if (packetData.PacketId != 0)
{
isIdle = false;
ProcessRecvPacket(packetData.ClientIndex, packetData.PacketId, packetData.DataSize, packetData.pDataPtr);
}
auto task = mRedisMgr->TakeResponseTask();
if (task.TaskId != (UINT16)RedisTaskID::INVALID)
{
isIdle = false;
ProcessRecvPacket(task.UserIndex, task.TaskId, task.DataSize, task.pData);
task.Release();
}
if (isIdle)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
void PacketManager::ProcessLogin(UINT32 clientIndex_, UINT16 packetSize_, char* pPacket_)
{
if (LOGIN_REQUEST_PACKET_SIZE != packetSize_)
{
return;
}
auto pLoginReqPacket = reinterpret_cast<LOGIN_REQUEST_PACKET*>(pPacket_);
auto pUserID = pLoginReqPacket->UserID;
std::cout << "requested user id = " << pUserID << std::endl;
LOGIN_RESPONSE_PACKET loginResPacket;
loginResPacket.PacketId = (UINT16)PACKET_ID::LOGIN_RESPONSE;
loginResPacket.PacketLength = sizeof(LOGIN_RESPONSE_PACKET);
// Max 접속자 수 초과
if (mUserManager->GetCurrentUserCnt() >= mUserManager->GetMaxUserCnt())
{
std::cout << "Server is Full\n";
loginResPacket.Result = (UINT16)ERROR_CODE::LOGIN_USER_USED_ALL_OBJ;
SendPacketFunc(clientIndex_, sizeof(LOGIN_RESPONSE_PACKET), (char*)&loginResPacket);
return;
}
// 중복 접속
if (mUserManager->FindUserIndexByID(pUserID) != -1)
{
std::cout << "Redundant Connection Fail\n";
loginResPacket.Result = (UINT16)ERROR_CODE::LOGIN_USER_ALREADY;
SendPacketFunc(clientIndex_, sizeof(LOGIN_RESPONSE_PACKET), (char*)&loginResPacket);
return;
}
else
{
RedisLoginReq dbReq;
CopyMemory(dbReq.UserID, pLoginReqPacket->UserID, (MAX_USER_ID_LEN + 1));
CopyMemory(dbReq.UserPW, pLoginReqPacket->UserPW, (MAX_USER_PW_LEN + 1));
RedisTask task;
task.UserIndex = clientIndex_;
task.TaskId = (UINT16)RedisTaskID::REQUEST_LOGIN;
task.DataSize = sizeof(RedisLoginReq);
task.pData = new char[task.DataSize];
CopyMemory(task.pData, (char*)&dbReq, task.DataSize);
mRedisMgr->PushRequest(task);
}
}
void PacketManager::ProcessLoginDBResult(UINT32 clientIndex_, UINT16 packetSize_, char *pPacket_)
{
std::cout << "ProcessLoginDBResult - UserIndex : " << clientIndex_ << std::endl;
auto pBody = (RedisLoginRes*)pPacket_;
if (pBody->Result == (UINT16)ERROR_CODE::NONE)
{
if (mUserManager->FindUserIndexByID(pBody->UserID) == -1)
mUserManager->AddUser(pBody->UserID, clientIndex_);
std::cout << "Login to Redis user id : " << pBody->UserID << std::endl;
}
LOGIN_RESPONSE_PACKET loginResPacket;
loginResPacket.PacketId = (UINT16)PACKET_ID::LOGIN_RESPONSE;
loginResPacket.PacketLength = sizeof(LOGIN_RESPONSE_PACKET);
loginResPacket.Result = pBody->Result;
SendPacketFunc(clientIndex_, sizeof(LOGIN_RESPONSE_PACKET), (char*)&loginResPacket);
}
'개인공부 > IOCP 서버 제작 실습' 카테고리의 다른 글
IOCP 서버 제작 실습 9 (0) | 2023.03.27 |
---|---|
IOCP 서버 제작 실습 8 (0) | 2023.03.27 |
IOCP 서버 제작 실습 6 (0) | 2023.03.23 |
IOCP 서버 제작 실습 5 (0) | 2023.03.23 |
IOCP 서버 제작 실습 4 (0) | 2023.03.22 |
댓글