I'm FanJae.

[C++ Server, C# Client] Day 4. Server 핸들러 로직 분할 본문

Toy Project/Multi Room Cheating Server

[C++ Server, C# Client] Day 4. Server 핸들러 로직 분할

FanJae 2024. 9. 29. 23:45

1. Handler Logic 분할 이유

서버가 처리하는게 많아지면 함수를 분리하는 것이 보기가 훨씬 깔끔해진다.

- 구현하는 입장에서도 특정 이벤트에 대한 처리를 어디서 하는지 확실히 알 수 있어 편해진다.


2. Client_Handle.h

const std::string CLOSE_SOCKET = "/Close_Socket";
const std::string COMPLETE_CREATE_ROOM = "/Complete_Create_Room";
const std::string EXIST_ROOM = "/Exist_Room";
const std::string NOT_EXIST_ROOM = "/Not_Exist_Room";
const std::string NO_ROOM = "/No_Room";
const std::string EXIT_ROOM = "/Exit_Room";
const std::string GET_CHATTING_ROOM = "/Get_Chatting_Room";
const std::string HANDLE_CREATE_CHATTING_ROOM = "/Create_Chatting_Room ";
const std::string HANDLE_JOIN_CHATTING_ROOM = "/Join_Chatting_Room ";

void ConnectClient(SOCKET clientSocket);

class ClientEventHandler
{
private:
	SOCKET socket;
	std::string roomName;
	
	void Handle_Get_Chatting_Room();
	void Handle_Exit_Room();
	void Handle_Create_Chatting_Room(const std::string& message);
	void Handle_Join_Chatting_Room(const std::string& message);
public:
	ClientEventHandler(SOCKET clientSocket);
	bool handleMessage(const std::string& message);
};

- ClientEventHandler 라는 객체를 만들었다.

- 이 객체는 클라이언트가 연결된 시점부터 연결이 끊기는 순간까지만 할당되고 공유자원도 아니다.


3. Client_Handle.cpp()

 

3-1. ConnectClient() 구현

void ConnectClient(SOCKET clientSocket)
{
	ClientEventHandler handler(clientSocket);
	char buffer[1024];
	std::string str_buffer;
	
	std::cout << "[Log] Client Connect! " << "clientSocket : "  << clientSocket << std::endl;

	while (true)
	{
		bool message_handle = false;
		int recv_length = recv(clientSocket, buffer, sizeof(buffer), 0);
		if (recv_length <= 0)
		{
			break;
		}
		buffer[recv_length] = '\0';
		str_buffer = buffer;
		std::cout << "[Log] " << str_buffer << std::endl;
		message_handle = handler.handleMessage(str_buffer);
		if (!message_handle) break;
	}
	closesocket(clientSocket);
}

- 서버가 연결을 시도하면 가장 먼저 이 작업이 반복적으로 수행된다.

- 서버는 연결된 소켓으로 부터 계속 메시지를 받아오고, 이 메시지를 받아서 handleMessage 함수에 넘겨준다.

- 그러면 이 함수에서 각 이벤트에 맞게 처리하도록 구현을 하였다.

- recv()는 받은 문자열의 길이를 반환하기 때문에 0이하이면 소켓을 닫도록 처리한다.

 

3-2. handleMessage() 구현

bool ClientEventHandler::handleMessage(const std::string& message)
{

	if(message.substr(0,GET_CHATTING_ROOM.length()) == GET_CHATTING_ROOM)
	{
		Handle_Get_Chatting_Room();
	}
	else if (message.substr(0, EXIT_ROOM.length()) == EXIT_ROOM)
	{
		Handle_Exit_Room();
	}
	else if (message.substr(0, HANDLE_CREATE_CHATTING_ROOM.length()) == HANDLE_CREATE_CHATTING_ROOM)
	{
		Handle_Create_Chatting_Room(message.substr(HANDLE_CREATE_CHATTING_ROOM.length(), message.length()));
	}
	else if (message.substr(0, HANDLE_JOIN_CHATTING_ROOM.length()) == HANDLE_JOIN_CHATTING_ROOM)
	{
		Handle_Join_Chatting_Room(message.substr(HANDLE_JOIN_CHATTING_ROOM.length(), message.length()));
	}
	else if (message.substr(0, CLOSE_SOCKET.length()) == CLOSE_SOCKET)
	{
		std::cout << "[Log] : Client disconnected : " << message << std::endl;
		return false;
	}
	else
	{
		std::cout << "[Log] : roomName : " << this->roomName << std::endl;
		for (SOCKET target_socket : chatRooms[this->roomName]) 
		{
			send(target_socket, message.c_str(),message.length(), 0);
		}
	}
	return true;
}

- 각 이벤트(Client_Handle.h)에 따라서, 적당히 이벤트를 처리하도록 만든다.

- 본인의 경우는 Client가 메시지를 보낼때, 방 이름을 보내는 경우에는 /Join_Room 방 이름과 같은 형태로 약속했다.

- 클라이언트 측에서 닫기 이벤트등으로 소켓을 닫는다면 서버에서도 Client와의 연결을 끊도록 만든다.

 

3-3. handleMessage() 구현 (메시지 처리)

void ClientEventHandler::Handle_Get_Chatting_Room()
{
	std::string room_List = "";
	if (chatRooms.empty())
	{
		room_List += NO_ROOM;
	}
	else
	{
		for (auto& room : chatRooms)
		{
			room_List += room.first + "\n";
		}
	}
	send(socket, room_List.c_str(), room_List.length(), 0);
}

- 채팅룸이 존재하는 경우에는 채팅룸의 리스트를 보낸다.

- 하지만 채팅룸이 없는 경우에는 NO_ROOM("/No_Room")을 보내서 처리한다.

 

3-4. Handle_Exit_Room() 구현 (채팅방 나가기)

void ClientEventHandler::Handle_Exit_Room()
{
	chatRooms[roomName].erase(this->socket);
	if (chatRooms[roomName].empty()) 
	{
		chatRooms.erase(roomName);
	}
}

- 사용자가 방을 나갔다는 것은 특정 방에 인원이 제거 된다는 것이다.

- 해당 인원을 방에서 제거하고, 방에 혹시 아무도 없다면 그 방은 삭제 해준다.

 

3-5. Handle_Create_Chatting_Room() 구현 (채팅방 생성)

void ClientEventHandler::Handle_Create_Chatting_Room(const std::string& message)
{
	this->roomName = message;
	std::string send_message = "";
	if (chatRooms.find(roomName) != chatRooms.end())
	{
		send_message = EXIST_ROOM;
		send(socket, send_message.c_str(), send_message.length(), 0);
	}
	else
	{
		std::cout << "[Log] : Client Create Room : " << message << std::endl;
		chatRooms[message].insert(socket);
		send_message = roomName;
		send(socket, send_message.c_str(), send_message.length(), 0);
	}
}

채팅 방이 있는지 확인하고 없으면 만들어준다. 있으면 EXIST_ROOM("/Exist_Room") 을 보내도록 처리했다.

 

3-6. Handle_Join_chatting_Room() 구현 (채팅방 입장)

void ClientEventHandler::Handle_Join_Chatting_Room(const std::string& message)
{
	this->roomName = message;
	std::string send_message = "";
	if (chatRooms.find(roomName) != chatRooms.end())
	{
		send_message = roomName;
		chatRooms[message].insert(socket);
		send(socket, send_message.c_str(), send_message.length(), 0);
	}
	else
	{
		send_message = NOT_EXIST_ROOM;
		send(socket, send_message.c_str(), send_message.length(), 0);
	}
}

채팅 방이 존재하면, 해당 방에 입장 (Map에 insert로 처리하였다.)

- 없으면, NOT_EXIST_ROOM("/Not_Exist_Room")을 보내도록 처리한다.

 

- 위와 같이 처리한 이후에 이제 서버는 어떤 부분에 동기화가 필요할지 체크해야 한다.

 

TODO

1) Server

 - 동기화 처리 필요

2) Client

- 각 상황에 맞는 버튼 이벤트 구현 필요

 

Comments