Thinking Different




서버 프로젝트를 지난 시간에 끝 이번에는 클라이언트 부분을 작성하도록 하겠습니다.

서버쪽에 비해서 클라이언트는 생각보다 작성하는 내용이 비교적 적습니다. 서버는 모든 클라이언트의 관리와 패킷 처리를 해야 되는 점에서 많은 코드가 필요하지만 클라이언트는 단순히 서버에서 오는 패킷을 처리하는 부분만 해주면 되기 때문에 생각보다 간결합니다.

 

CChatClient 부분에 대해서 작성합니다. 여기서 가장 중요한 코드는 역시 Flatbuffers 부분을 처리하는 ProcessPacket 함수 부분이라고 할 수 있습니다.

역시 Flatbuffers가 직렬화에 있어서는 아주 간편하고 사용하기 쉽다고 느껴집니다. 속도 또한 강점이므로 많은 게임 서버 프로그래밍에 이용되는 이유 중에 하나일거라고 생각됩니다.

 

 

ChatClient.h

#pragma once

class CChatClient
{
public:
	CChatClient(boost::asio::io_context& io_context, const tcp::resolver::results_type& endpoints);
	~CChatClient();

public:
	void SetLogin(bool bLogin) { m_bIsLogin = bLogin; }
	bool GetLogin() { return m_bIsLogin; }

	bool IsConnect() { return m_Socket.is_open(); }

	void Close();
	void Write(const CMessage& msg);

	void ProcessPacket(const uint8_t* pData, size_t size);

private:
	void handle_write();
	void handle_connect(const tcp::resolver::results_type& endpoints);
	void handle_read_header();
	void handle_read_body();

private:
	bool m_bIsLogin = true;

	boost::asio::io_context& m_IoContext;
	boost::asio::ip::tcp::socket m_Socket;

	CMessage m_ReadMessage;
	std::deque<CMessage> m_WriteMessage;
};

 

 

ChatClient.cpp

#include "stdafx.h"
#include "ChatClient.h"

CChatClient::CChatClient(boost::asio::io_context& io_context, const tcp::resolver::results_type& endpoints)
	: m_IoContext(io_context),
	m_Socket(io_context),
	m_bIsLogin(false)
{
	handle_connect(endpoints);
}

CChatClient::~CChatClient()
{
}

void CChatClient::handle_connect(const tcp::resolver::results_type& endpoint)
{
	boost::asio::async_connect(m_Socket, endpoint, 
		[this](boost::system::error_code error, tcp::endpoint)
		{
			if (!error)
			{
				std::cout << "서버 접속 성공" << std::endl;
				std::cout << "이름을 입력하세요 : ";

				handle_read_header();
			}
			else
			{
				std::cout << "서버 접속 실패. error No: " << error.value() << " error Message: " << error.message() << std::endl;
			}
		});
}

void CChatClient::handle_read_header()
{
	boost::asio::async_read(m_Socket, 
		boost::asio::buffer(m_ReadMessage.data(), CMessage::header_length),
		[this](boost::system::error_code error, std::size_t)
		{
			if (!error && m_ReadMessage.decode_header())
			{
				handle_read_body();
			}
			else
			{
				if (error == boost::asio::error::eof)
				{
					std::cout << "클라이언트와 연결이 끊어졌습니다!" << std::endl;
				}
				else
				{
					std::cout << "error No: " << error.value() << " error Message: " << error.message() << std::endl;
				}

				m_Socket.close();
			}
		});
}

void CChatClient::handle_read_body()
{
	boost::asio::async_read(m_Socket, 
		boost::asio::buffer(m_ReadMessage.body(), m_ReadMessage.body_length()),
		[this](boost::system::error_code error, std::size_t)
		{
			if (!error)
			{
				ProcessPacket(m_ReadMessage.body(), m_ReadMessage.body_length());

				handle_read_header();
			}
			else
			{
				if (error == boost::asio::error::eof)
				{
					std::cout << "클라이언트와 연결이 끊어졌습니다!" << std::endl;
				}
				else
				{
					std::cout << "error No: " << error.value() << " error Message: " << error.message() << std::endl;
				}

				m_Socket.close();
			}
		});
}

void CChatClient::Close()
{
	boost::asio::post(m_IoContext, [this]() { m_Socket.close(); });
}

void CChatClient::Write(const CMessage& msg)
{
	boost::asio::post(m_IoContext,
		[this, msg]()
		{
			m_WriteMessage.push_back(msg);

			if (!m_WriteMessage.empty())
			{
				handle_write();
			}
		});
}

void CChatClient::handle_write()
{
	boost::asio::async_write(m_Socket,
		boost::asio::buffer(m_WriteMessage.front().data(), m_WriteMessage.front().length()),
		[this](boost::system::error_code error, std::size_t /*length*/)
		{
			if (!error)
			{
				m_WriteMessage.pop_front();

				if (!m_WriteMessage.empty())
				{
					handle_write();
				}
			}
			else
			{
				m_Socket.close();
			}
		});
}

void CChatClient::ProcessPacket(const uint8_t* pData, size_t size)
{
	auto verifier = flatbuffers::Verifier(pData, size);

	if (fbs::VerifyRootBuffer(verifier))
	{
		auto root = fbs::GetRoot(pData);

		switch (root->packet_type())
		{
		case fbs::Packet_S2C_CONNECT_RES:
		{
			auto raw = static_cast<const fbs::S2C_CONNECT_RES*>(root->packet());

			if (raw->b_success() == true)
			{
				SetLogin(true);

				flatbuffers::FlatBufferBuilder builder;

				auto data = fbs::CreateC2S_ENTER_REQ(builder);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_C2S_ENTER_REQ, data.Union());

				builder.Finish(packet);

				Write(CMessage(builder.GetBufferPointer(), builder.GetSize()));
			}
			else
			{
				std::cout << "클라이언트 로그인 실패!" << std::endl;
			}

			break;
		}

		case fbs::Packet_S2C_ENTER_RES:
		{
			auto raw = static_cast<const fbs::S2C_ENTER_RES*>(root->packet());

			cout << raw->name()->c_str() << " 님이 접속하였습니다." << endl;

			break;
		}

		case fbs::Packet_S2C_LEAVE_RES:
		{
			auto raw = static_cast<const fbs::S2C_LEAVE_RES*>(root->packet());

			cout << raw->name()->c_str() << " 님이 퇴장하였습니다." << endl;

			break;
		}

		case fbs::Packet_S2C_CHAT_RES:
		{
			auto raw = static_cast<const fbs::S2C_CHAT_RES*>(root->packet());
			std::cout << raw->name()->c_str() << ": " << raw->message()->c_str() << std::endl;

			break;
		}
		}
	}
	else
	{
		throw std::runtime_error("Ill-formed message");
	}
}