일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- virtual function
- new&delete
- conversion constructor
- virtual function table
- c++ multi chatting room
- return by reference
- delete function
- std::endl
- c++ basic practice
- discord bot
- std::ostream
- member function pointer
- suffix return type
- this call
- vector size
- dynamic_cast
- std::cout
- operator overloading
- increment operator
- std::vector
- base from member
- vector capacity
- C++
- virtual inheritance
- diamond inheritance
- 더 지니어스 양면포커
- placement new
- constructor
- pointer to member data
- virtual destructor
- Today
- Total
I'm FanJae.
[Two Faced Poker] Day 5. Server 리팩토링 I. 대공사 본문
1. Server 리팩토링을 결심한 이유
① Client_Event_Handler와 Game_Manager간의 독립적인 클래스라고 하기에 다소 모호한 설계 구조 개선
- 기존 내가 만들었던 Client_Event_Handler는 거의 대부분의 Event를 처리하고 있다.
- Message 수신 자체는 Client_Event_Handler가 담당하더라도 다른 Manager들이 이를 처리하도록 개선하였다.
② 서버가 상태 값을 가지고 있지 않음으로써 발생하는 문제 해소.
- 준비 / 완료등을 Client 가 보는 기준에서 처리하는 것은 옳지 않다고 생각해서.. 바꿔보고자 한다.
- Client에서 보낸 메시지를 검증을 하더라도 이 부분은 서버가 반드시 가지고 있어야 한다.
③ Packet의 Header 정보 부재
- TCP/IP에서는 원래 n바이트를 보냈다고 이를 받는 쪽에서 n바이트를 받는다는 보장이 없다.
- 이 부분에 대해서 너무 생각없이 처리했다. 이 부분을 제대로 처리해보도록 하겠다.
2. Server 구조 개선
- 기존 Client_Handler가 하는 역할이 단순한 메시지 핸들뿐 아니라 실질적인 로직도 모두 처리하고 있었다.
- 따라서 이 부분을 개선하고, 각 Manager를 생성하고, 각각의 이벤트를 처리한다.
2-1. Map Key값 개선.
static std::map<int, std::shared_ptr<Game_Manager>> gameManagers;
static std::map<int, std::shared_ptr<Room_Manager>> roomManagers;
- 초기 설계 의도는 제목으로 중복된 방을 만들지 않게할 의도였다.
- 하지만 Key값이 String인건 그렇게 좋은 방법도 아닐뿐만 아니라, int (4byte) 이상 방이 만들어질 가능성도 적다.
2-2. Room Manager 추가
#include <string>
#include <WinSock2.h>
#include <set>
#include <map>
#include <memory>
#include "foundation.h"
const std::string EXIT_ROOM_COMPLETE = "/Exit_Room_Complete";
const std::string ROOM_CLIENT_EVENT = "/Room_Event ";
const std::string UPDATE_ID = "Update_ID ";
const std::string UPDATE_READY_STATE = "Update_Ready_State ";
const std::string READY = "READY";
const std::string DONE = "DONE";
enum class TargetType
{
SELF,
OTHERS,
ALL
};
class Room_Manager
{
private:
std::string roomName;
std::set<SOCKET> sockets;
std::map<std::string, std::shared_ptr<User>> users;
std::map<SOCKET, std::string> socketUserID;
int roomNumber;
int roomCount;
public:
Room_Manager(const int roomNumber, const std::string& roomName);
static std::shared_ptr<Room_Manager> createRoom(const int roomNumber, const std::string& roomName);
void broadcast_Message(const std::string& message, SOCKET sender_socket, TargetType target_type);
std::string getUserIDFromSocket(const SOCKET& socket) const;
std::pair<std::string, bool> getOtherUserInfo(const std::string& userID);
std::pair<std::string, bool> getThisUserInfo(const std::string& userID);
void addUser(const std::string& userID, SOCKET ID);
void removeUser(const std::string& userID, SOCKET ID);
void userUpdate(SOCKET ID);
bool isroomEmpty() const;
int getroomCount() const;
std::string getroomName() const;
void roomCountSet(const std::string& type);
};
- Room Manager라는 것을 추가했다.
- Client_Handle에서 Room Manager를 통해서 해당 로직을 처리하는 방식으로 수정했다.
① Before Handle_Create_Chatting_Room()
void ClientEventHandler::Handle_Create_Chatting_Room(const std::string& message)
{
this->roomName = message;
std::string send_message = "";
{
const std::lock_guard<std::mutex> lock(chatRoom_mutex);
if (chatRooms.find(roomName) != chatRooms.end())
{
send_message = EXIST_ROOM;
}
else
{
std::cout << "[Log] : Client Create Room : " << message << std::endl;
chatRooms[message].insert(socket);
Room_count[message] = 0;
std::shared_ptr<Game_Manager> roomGameManager = std::make_shared<Game_Manager>(roomName);
gameManagers[roomName] = roomGameManager;
send_message = roomName;
}
}
send(socket, send_message.c_str(), send_message.length(), 0);
}
② After Handle_Create_Chatting_Room()
void ClientEventHandler::Handle_Create_Chatting_Room(const std::string& roomName)
{
const std::lock_guard<std::mutex> lock(chatRoom_mutex);
std::string send_message = std::to_string(roomcountNumber);
std::shared_ptr<Room_Manager> roomManager = Room_Manager::createRoom(roomcountNumber, roomName);
roomManagers[roomcountNumber] = roomManager;
std::shared_ptr<Game_Manager> gameManager = std::make_shared<Game_Manager>(roomcountNumber, roomManager);
gameManagers[roomcountNumber] = gameManager;
roomcountNumber++;
std::cout << "[System] : Room_Number : " << send_message << "Room_Name : " << roomName << std::endl;
SendPacket(this->socket, send_message);
}
- 객체를 생성할 때 Smart Pointer를 사용했다.
- 방을 생성할 때 만들어지는 Room_Manager와 Game_Manager는 방이 삭제되는 시점(erase) 시점에 반환된다.
③ Handle_Exit_Room()
void ClientEventHandler::Handle_Exit_Room()
{
std::cout << "[System] : " << "Handle_exit_Room" << std::endl;
std::map<int, std::shared_ptr<Room_Manager>>::iterator it;
it = roomManagers.find(this->roomNumber);
if (it != roomManagers.end())
{
std::cout << "[System] : Room Find.";
it->second->removeUser(this->ID, this->socket);
if (it->second->isroomEmpty() == true)
{
roomManagers.erase(this->roomNumber);
gameManagers.erase(this->roomNumber);
this->roomNumber = 0;
}
}
else
{
std::cout << "[System] : Room Not Found";
return ;
}
}
- 객체를 삭제하는 시점에서 자원이 자동으로 반환하도록 처리했다.
※ 기존 Client_Handler에서 하던 작업의 대부분은 옮겼다.
2-3. Room Manager 내부 처리
void Room_Manager::addUser(const std::string& userID, SOCKET socket)
{
users[userID] = std::make_shared<User>(userID);
sockets.insert(socket);
socketUserID[socket] = userID;
roomCountSet("PLUS");
}
void Room_Manager::removeUser(const std::string& userID, SOCKET socket)
{
std::string exit_message = EXIT_ROOM_COMPLETE;
broadcast_Message(exit_message, socket,TargetType::SELF);
users.erase(userID);
sockets.erase(socket);
socketUserID.erase(socket);
roomCountSet("MINUS");
exit_message = userID + " has exited.";
std::cout << "System : " << exit_message << std::endl;
broadcast_Message(exit_message,socket,TargetType::OTHERS);
}
void Room_Manager::userUpdate(SOCKET clientSocket)
{
std::string update_message;
if (roomCount > 1)
{
for (SOCKET target_socket : sockets)
{
if (target_socket == clientSocket)
{
std::string ID = getUserIDFromSocket(clientSocket);
std::pair<std::string, bool> userData = getThisUserInfo(ID);
update_message = ROOM_CLIENT_EVENT + UPDATE_ID + userData.first;
std::cout << "Update_message : " << update_message << std::endl;
broadcast_Message(update_message, clientSocket, TargetType::OTHERS);
if (userData.second == false)
{
update_message = ROOM_CLIENT_EVENT + UPDATE_READY_STATE + READY;
}
else
{
update_message = ROOM_CLIENT_EVENT + UPDATE_READY_STATE + DONE;
}
std::cout << "Update_message : " << update_message << std::endl;
broadcast_Message(update_message, clientSocket, TargetType::OTHERS);
}
else
{
std::string ID = getUserIDFromSocket(target_socket);
std::pair<std::string, bool> userData = getThisUserInfo(ID);
update_message = ROOM_CLIENT_EVENT + UPDATE_ID + userData.first;
std::cout << "Update_message : " << update_message << std::endl;
broadcast_Message(update_message, clientSocket, TargetType::SELF);
if (userData.second == false)
{
update_message = ROOM_CLIENT_EVENT + UPDATE_READY_STATE + READY;
}
else
{
update_message = ROOM_CLIENT_EVENT + UPDATE_READY_STATE + DONE;
}
std::cout << "Update_message : " << update_message << std::endl;
broadcast_Message(update_message, clientSocket, TargetType::SELF);
}
}
}
}
- Client_Handle에서 해야만 하는 작업이 아니라면, 가급적 Manager에서 처리하도록 다 갈아 엎었다..
- 이전에 다소 불필요하게 만들어놨던 이벤트(상수값)도 정리해서 가급적 비슷한 이벤트는 묶었다.
- 이를 적당히 묶어서, 불필요한 이벤트는 정리하고 반드시 구분이 필요한 이벤트만 남겼다.
2-4. Packet
#include "Packet.h"
#include <iostream>
#include <WinSock2.h>
std::string ReceivePacket(SOCKET clientSocket)
{
char buffer[1025];
uint32_t Packet_length = 0;
int byte_length = recv(clientSocket, reinterpret_cast<char*>(&Packet_length), sizeof(Packet_length), 0);
int errorCode = errno;
char errorMessage[256];
if (byte_length == 0)
{
std::cerr << "Recive : Connection closed." << strerror_s(errorMessage,sizeof(errorMessage),errorCode) << std::endl;
return "";
}
if (byte_length < 0)
{
std::cerr << "Recive : Error" << strerror_s(errorMessage, sizeof(errorMessage), errorCode) << std::endl;
return "";
}
Packet_length = ntohl(Packet_length);
if (Packet_length > 1024)
{
std::cerr << "Packet too large. Packet_length value changed." << std::endl;
Packet_length = 1024;
}
byte_length = recv(clientSocket, buffer, Packet_length, 0);
if (byte_length <= 0)
{
std::cerr << "Received Error." << std::endl;
return "";
}
buffer[Packet_length] = '\0';
std::string test = buffer;
std::cout << "[System] Receive " << Packet_length << " " << test << std::endl;
return test;
}
void SendPacket(SOCKET clientSocket, const std::string& message)
{
uint32_t Packet_length = htonl(message.size());
send(clientSocket, reinterpret_cast<char*>(&Packet_length), sizeof(Packet_length), 0);
std::cout << "[System] : Send message_size : " << message.size() << "message : " << message << std::endl;
send(clientSocket, message.c_str(), message.size(), 0);
}
- 대체 무슨 생각으로 내가 이걸 안 만들었을까..
- 그것보다 더 이상했던건 이전 Multi Room Chatting Server는 어떻게 문제가 없었을까...
- 이 리팩토링의 가장 큰 핵심은 여기라고 생각한다.
- Server와 Client에서 송수신시 길이 정보를 우선 읽어들인 이후, 길이 정보를 먼저 보낸 이후에 메시지를 처리한다.
'Toy Project > Two Faced Poker' 카테고리의 다른 글
[Two Faced Poker] Day 6. Game Logic 처리 I (1) | 2024.10.31 |
---|---|
[Two Faced Poker] Day 5. Server 리팩토링 II. 서버 리팩토링에 따른 Client 대공사 (5) | 2024.10.20 |
[Two Faced Poker] Day 4. 방에 아이디 정보나 준비 상태 정보를 받아오기 (7) | 2024.10.16 |
[Two Faced Poker] Day 3. Server 방 인원수 제한 및 GameManager 추가, Client GUI 버튼 추가 (1) | 2024.10.15 |
[Two Faced Poker] Day 2. Server 로직 개선 및 Client Game 판 구성. (2) | 2024.10.14 |