Thinking Different




 

콘솔 채팅 프로그램 소스에서 약간 변경된 부분은 CChatServer 클래스 입니다.

 

가장 중요한 코드 부분은 콘솔모드와는 달리 GUI 모드는 채팅방에 들어와 있는 모든 유저를 받아오는 코드가 필요해서 USER_LIST 부분의 패킷을 추가하였습니다.

 

채팅방을 접속하면 나의 접속을 알려주며 다시 접속된 채팅방에 있는 모든 유저목록을 받아와서 클라이언트 MFC 채팅 프로그램에 보여주는 식으로 작동됩니다.

 

 

Flatbuffers 에서는 유저 목록 전송을 간단히 하기 위해서 vector schema를 사용할 수 있습니다.

모든 유저 이름을 벡터 컨테이너로 만들어서 CreateVectorOfString() 함수를 통해서 vector schema로 직렬화합니다.

 

 

 

CChatServer.h

#pragma once
#include <set>

class CSession;

class CChatServer
{
public:
	CChatServer(const unsigned short PORT_NUMBER);
	~CChatServer();

public:
	void Start();
	void Stop();

	void EndSession(std::shared_ptr<CSession>);
	void ProcessPacket(std::shared_ptr<CSession>, const uint8_t*, size_t);

private:
	void handle_accept();
	void WritePacketAll(const CMessage&);

private:
	boost::asio::io_context m_IoContext;
	boost::asio::ip::tcp::acceptor m_Acceptor;

	boost::shared_ptr<boost::asio::io_service::work> async_work;
	boost::thread_group async_thread;


	std::set<std::shared_ptr<CSession>> m_SessionList;
};

 

CChatServer.cpp

#include "stdafx.h"
#include "Session.h"
#include "ChatServer.h"

CChatServer::CChatServer(const unsigned short PORT_NUMBER)
    : m_Acceptor(m_IoContext, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), PORT_NUMBER))
{
}

CChatServer::~CChatServer()
{
	m_SessionList.clear();
}

void CChatServer::Start()
{
    async_work.reset(new boost::asio::io_context::work(m_IoContext));
    async_thread.create_thread(boost::bind(&boost::asio::io_context::run, &m_IoContext));

    std::cout << "서버 시작... (Port : " << m_Acceptor.local_endpoint().port() << ")" << std::endl;
	
    handle_accept();
}

void CChatServer::Stop()
{
    async_work.reset();
    m_IoContext.stop();
    async_thread.join_all();

    std::cout << "서버 종료..." << std::endl;
}

void CChatServer::EndSession(std::shared_ptr<CSession> pSession)
{
	std::cout << "클라이언트 접속 종료 (" << pSession->GetName() << ")" << std::endl;

	pSession->Socket().close();
	m_SessionList.erase(pSession);
}

void CChatServer::handle_accept()
{
	m_Acceptor.async_accept(
		[this](boost::system::error_code error, tcp::socket socket)
		{
			if (!error)
			{
				std::cout << "클라이언트 접속 성공" << std::endl;

				auto pSession = std::make_shared<CSession>(std::move(socket), this);

				m_SessionList.insert(pSession);

				pSession->Start();
			}
			else
			{
				std::cout << "error No: " << error.value() << " error Message: " << error.message() << std::endl;
			}

			handle_accept();
		});
}

void CChatServer::WritePacketAll(const CMessage& msg)
{
	for (auto it : m_SessionList)
		it->WritePacket(msg);
}

void CChatServer::ProcessPacket(std::shared_ptr<CSession> pSession, 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_C2S_CONNECT_REQ:	// 클라이언트 접속 요청
			{
				auto raw = static_cast<const fbs::C2S_CONNECT_REQ*>(root->packet());

				flatbuffers::FlatBufferBuilder builder;

				auto data = fbs::CreateS2C_CONNECT_RES(builder, true);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_CONNECT_RES, data.Union());

				builder.Finish(packet);

				pSession->WritePacket(CMessage(builder.GetBufferPointer(), builder.GetSize()));
				pSession->SetName(raw->name()->c_str());

				std::cout << "클라이언트 로그인 성공 Name: " << pSession->GetName() << std::endl;


				builder.Clear();

				auto name = builder.CreateString(raw->name()->c_str());
				auto datas = fbs::CreateS2C_ENTER_RES(builder, name);
				auto packets = fbs::CreateRoot(builder, fbs::Packet_S2C_ENTER_RES, datas.Union());
				builder.Finish(packets);

				for (auto it : m_SessionList)
				{
					if(it != pSession)
						it->WritePacket(CMessage(builder.GetBufferPointer(), builder.GetSize()));
				}

				break;
			}

			case fbs::Packet_C2S_LEAVE_REQ:		// 방 퇴장 요청
			{
				flatbuffers::FlatBufferBuilder builder;

				auto name = builder.CreateString(pSession->GetName());
				auto data = fbs::CreateS2C_LEAVE_RES(builder, name);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_LEAVE_RES, data.Union());

				builder.Finish(packet);

				WritePacketAll(CMessage(builder.GetBufferPointer(), builder.GetSize()));

				break;
			}

			case fbs::Packet_C2S_USERLIST_REQ:		// 유저 리스트 요청
			{
				vector<string> v;
				for (auto it : m_SessionList)
				{
					v.push_back(it->GetName());
				}

				flatbuffers::FlatBufferBuilder builder;

				auto vs = builder.CreateVectorOfStrings(v);
				auto data = fbs::CreateS2C_USERLIST_RES(builder, vs);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_USERLIST_RES, data.Union());

				builder.Finish(packet);

				pSession->WritePacket(CMessage(builder.GetBufferPointer(), builder.GetSize()));

				break;
			}

			case fbs::Packet_C2S_CHAT_REQ:		// 채팅 요청
			{
				auto raw = static_cast<const fbs::C2S_CHAT_REQ*>(root->packet());

				flatbuffers::FlatBufferBuilder builder;

				auto name = builder.CreateString(pSession->GetName());
				auto msg = builder.CreateString(raw->message()->c_str());

				auto data = fbs::CreateS2C_CHAT_RES(builder, name, msg);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_CHAT_RES, data.Union());

				builder.Finish(packet);

				WritePacketAll(CMessage(builder.GetBufferPointer(), builder.GetSize()));

				break;
			}

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